diff --git a/pom.xml b/pom.xml index 44f695db5..3ce4e8b57 100644 --- a/pom.xml +++ b/pom.xml @@ -42,6 +42,8 @@ + ttl-core + ttl2-compatible ttl-integrations/vertx4-ttl-integration diff --git a/ttl-core/pom.xml b/ttl-core/pom.xml new file mode 100644 index 000000000..e8f5680ec --- /dev/null +++ b/ttl-core/pom.xml @@ -0,0 +1,340 @@ + + 4.0.0 + + com.alibaba.ttl3 + ttl3-parent + 3.x-SNAPSHOT + ../pom.xml + + + ttl-core + jar + TransmittableThreadLocal(TTL) + + 📌 The missing Java™ std lib(simple & 0-dependency) for framework/middleware, + provide an enhanced InheritableThreadLocal that transmits values between threads even using thread pooling components. + + https://github.com/alibaba/transmittable-thread-local + 2022 + + + + Apache 2 + https://www.apache.org/licenses/LICENSE-2.0.txt + repo + A business-friendly OSS license + + + + scm:git:git@github.com:alibaba/transmittable-thread-local.git + scm:git:git@github.com:alibaba/transmittable-thread-local.git + https://github.com/alibaba/transmittable-thread-local + + + https://github.com/alibaba/transmittable-thread-local/issues + GitHub Issues + + + AppVeyor + https://ci.appveyor.com/project/oldratlee/transmittable-thread-local + + + Alibaba + https://www.alibaba.com + + + + Jerry Lee + oldratlee + oldratlee(AT)gmail(DOT)com + + Developer + + +8 + https://github.com/oldratlee + Alibaba + https://www.alibaba.com + + + Yang Fang + driventokill + snoop(DOT)fy(AT)gmail(DOT)com + + Developer + + +8 + https://github.com/driventokill + Alibaba + https://www.alibaba.com + + + wuwen + wuwen5 + wuwen.55(AT)aliyun(DOT)com + + Developer + + +8 + https://github.com/wuwen5 + ofpay + https://www.ofpay.com + + + David Dai + LNAmp + 351450944(AT)qq(DOT)com + + Developer + + +8 + https://github.com/LNAmp + Alibaba + https://www.alibaba.com + + + + + + org.javassist + javassist + true + + + + + + + maven-jar-plugin + + + + + com.alibaba.ttl3.threadpool.agent.TtlAgent + ${project.build.finalName}.jar + false + true + false + + + + + + maven-shade-plugin + + + shade-when-package + package + + shade + + + + + javassist + com.alibaba.ttl3.threadpool.agent.transformlet.javassist + + + + + org.javassist:javassist + + + + + org.javassist:javassist + + META-INF/MANIFEST.MF + + + + true + + + + + + + + + config-for-jdk16+ + + [16,) + + + + + maven-surefire-plugin + + + **/*$* + **/JavassistTest* + + + + + + + + enable-ttl-agent-for-test + + + + ${project.build.directory}/${project.artifactId}-${project.version}.jar + + ttl.agent.logger:STDOUT + + + -javaagent:${ttl.built.agent.jar}=${ttl.agent.args},${ttl.agent.extra.args} + + -Drun-ttl-test-under-agent=true ${ttl.agent.extra.d.options} ${ttl.agent.jvm.arg} + + com.alibaba.demo.ttl.agent.AgentDemo + + + + + maven-surefire-plugin + + + 1 + true + + org.javassist:javassist + com.github.spotbugs:spotbugs-annotations + com.google.code.findbugs:jsr305 + org.jetbrains:annotations + + @{argLine} ${ttl.agent.jvm.args} + + + + + org.codehaus.mojo + exec-maven-plugin + + + -Xmx1g + -Xms256m + -ea + -server + -Duser.language=en + -Duser.country=US + + ${ttl.agent.jvm.arg} + + -classpath + + + ${exec.mainClass} + + + + + + + + gen-src + + + performRelease + true + + + + + + maven-source-plugin + + false + + + + maven-shade-plugin + + + shade-when-package + + true + + + + + + + + + gen-javadoc + + + performRelease + true + + + + + + maven-javadoc-plugin + + true + + + + https://docs.oracle.com/javase/10/docs/api/ + ${project.basedir}/src/package-list/java/ + + + https://static.javadoc.io/com.github.spotbugs/spotbugs-annotations/${spotbugs.annotations.version}/ + ${project.basedir}/src/package-list/spotbugs-annotations/ + + + + + + + + + gen-code-cov + + + + org.jacoco + jacoco-maven-plugin + + + com/alibaba/ttl3/threadpool/agent/**/*.class + com/alibaba/ttl3/TtlTimerTask.class + + + + + + + + diff --git a/ttl-core/src/api/overview.html b/ttl-core/src/api/overview.html new file mode 100644 index 000000000..c09fd1144 --- /dev/null +++ b/ttl-core/src/api/overview.html @@ -0,0 +1,9 @@ + + +

This is the API documentation for the + 📌 TransmittableThreadLocal(TTL), + The missing Java™ std lib(simple & 0-dependency) for framework/middleware, + provide an enhanced InheritableThreadLocal that transmits values between threads even using thread pooling components. +

+ + diff --git a/ttl-core/src/main/java/com/alibaba/crr/CrrTransmit.java b/ttl-core/src/main/java/com/alibaba/crr/CrrTransmit.java new file mode 100644 index 000000000..85a726bbf --- /dev/null +++ b/ttl-core/src/main/java/com/alibaba/crr/CrrTransmit.java @@ -0,0 +1,64 @@ +package com.alibaba.crr; + +import edu.umd.cs.findbugs.annotations.NonNull; + +/** + * Transmittance is completed by by methods {@link #capture() capture()} => + * {@link #replay(Object) replay(Object)} => {@link #restore(Object) restore(Object)} (aka {@code CRR} operations), + * + * @param the capture data type of transmittance + * @param the backup data type of transmittance + * @author Jerry Lee (oldratlee at gmail dot com) + */ +public interface CrrTransmit { + /** + * Capture. + *

+ * NOTE:
+ * do NOT return {@code null}. + * + * @return the capture data of transmittance + */ + @NonNull + C capture(); + + /** + * Replay. + *

+ * NOTE:
+ * do NOT return {@code null}. + * + * @param captured the capture data of transmittance, the return value of method {@link #capture()} + * @return the backup data of transmittance + */ + @NonNull + B replay(@NonNull C captured); + + /** + * Clear. + *

+ * NOTE:
+ * do NOT return {@code null}. + *

+ * Semantically, the code {@code `B backup = clear();`} is same as {@code `B backup = replay(EMPTY_CAPTURE);`}. + *

+ * The reason for providing this method is: + *

    + *
  1. lead to more readable code
  2. + *
  3. need not provide the constant {@code EMPTY_CAPTURE}.
  4. + *
+ * + * @return the backup data of transmittance + */ + @NonNull + B clear(); + + /** + * Restore. + * + * @param backup the backup data of transmittance, the return value of methods {@link #replay(Object)} or {@link #clear()} + * @see #replay(Object) + * @see #clear() + */ + void restore(@NonNull B backup); +} diff --git a/ttl-core/src/main/java/com/alibaba/crr/composite/Backup.java b/ttl-core/src/main/java/com/alibaba/crr/composite/Backup.java new file mode 100644 index 000000000..59ce00f75 --- /dev/null +++ b/ttl-core/src/main/java/com/alibaba/crr/composite/Backup.java @@ -0,0 +1,13 @@ +package com.alibaba.crr.composite; + +import org.jetbrains.annotations.ApiStatus; + +/** + * The composite backup data type of {@link CompositeCrrTransmit}. + * + * @author Jerry Lee (oldratlee at gmail dot com) + * @see CompositeCrrTransmit + */ +@ApiStatus.NonExtendable +public interface Backup { +} diff --git a/ttl-core/src/main/java/com/alibaba/crr/composite/Capture.java b/ttl-core/src/main/java/com/alibaba/crr/composite/Capture.java new file mode 100644 index 000000000..14fa37503 --- /dev/null +++ b/ttl-core/src/main/java/com/alibaba/crr/composite/Capture.java @@ -0,0 +1,13 @@ +package com.alibaba.crr.composite; + +import org.jetbrains.annotations.ApiStatus; + +/** + * The composite capture data type of {@link CompositeCrrTransmit}. + * + * @author Jerry Lee (oldratlee at gmail dot com) + * @see CompositeCrrTransmit + */ +@ApiStatus.NonExtendable +public interface Capture { +} diff --git a/ttl-core/src/main/java/com/alibaba/crr/composite/CompositeCrrTransmit.java b/ttl-core/src/main/java/com/alibaba/crr/composite/CompositeCrrTransmit.java new file mode 100644 index 000000000..166cab005 --- /dev/null +++ b/ttl-core/src/main/java/com/alibaba/crr/composite/CompositeCrrTransmit.java @@ -0,0 +1,167 @@ +package com.alibaba.crr.composite; + +import com.alibaba.crr.CrrTransmit; +import edu.umd.cs.findbugs.annotations.NonNull; + +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.CopyOnWriteArraySet; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * CompositeCrrTransmit transmit all {@link CrrTransmit} + * registered by {@link #registerCrrTransmit(CrrTransmit)}. + *

+ * Transmittance is completed by methods {@link #capture()} => + * {@link #replay(Capture)} => {@link #restore(Backup)} (aka {@code CRR} operations). + *

+ * NOTE:
+ * This implementation ignore the exception from {@code CRR} operations + * of registered {@link CrrTransmit}. + * + * @author Jerry Lee (oldratlee at gmail dot com) + */ +public class CompositeCrrTransmit implements CrrTransmit { + private static final Logger logger = Logger.getLogger(CompositeCrrTransmit.class.getName()); + + /** + * Capture all {@link CrrTransmit}. + * + * @return the captured values + */ + @NonNull + public Capture capture() { + final HashMap, Object> crrTransmit2Value = new HashMap<>(crrTransmitSet.size()); + for (CrrTransmit crrTransmit : crrTransmitSet) { + try { + crrTransmit2Value.put(crrTransmit, crrTransmit.capture()); + } catch (Throwable t) { + if (logger.isLoggable(Level.WARNING)) { + logger.log(Level.WARNING, "exception when capture for crrTransmit " + crrTransmit + + "(class " + crrTransmit.getClass().getName() + "), just ignored; cause: " + t, t); + } + } + } + return new Snapshot(crrTransmit2Value); + } + + /** + * Replay the captured values from {@link #capture()}, + * and return the backup values before replay. + * + * @param captured captured values {@link #capture()} + * @return the backup values before replay + * @see #capture() + */ + @NonNull + public Backup replay(@NonNull Capture captured) { + final Snapshot capturedSnapshot = (Snapshot) captured; + + final HashMap, Object> crrTransmit2Value = new HashMap<>(capturedSnapshot.crrTransmit2Value.size()); + for (Map.Entry, Object> entry : capturedSnapshot.crrTransmit2Value.entrySet()) { + CrrTransmit crrTransmit = entry.getKey(); + try { + Object transmitCaptured = entry.getValue(); + crrTransmit2Value.put(crrTransmit, crrTransmit.replay(transmitCaptured)); + } catch (Throwable t) { + if (logger.isLoggable(Level.WARNING)) { + logger.log(Level.WARNING, "exception when replay for crrTransmit " + crrTransmit + + "(class " + crrTransmit.getClass().getName() + "), just ignored; cause: " + t, t); + } + } + } + return new Snapshot(crrTransmit2Value); + } + + /** + * Clear all values, and return the backup values before clear. + *

+ * Semantically, the code {@code `Object backup = clear();`} is same as {@code `Object backup = replay(EMPTY_CAPTURE);`}. + *

+ * The reason for providing this method is: + * + *

    + *
  1. lead to more readable code
  2. + *
  3. need not provide the constant {@code EMPTY_CAPTURE}.
  4. + *
+ * + * @return the backup values before clear + * @see #replay(Capture) + */ + @NonNull + public Backup clear() { + final HashMap, Object> crrTransmit2Value = new HashMap<>(crrTransmitSet.size()); + for (CrrTransmit crrTransmit : crrTransmitSet) { + try { + crrTransmit2Value.put(crrTransmit, crrTransmit.clear()); + } catch (Throwable t) { + if (logger.isLoggable(Level.WARNING)) { + logger.log(Level.WARNING, "exception when clear for crrTransmit " + crrTransmit + + "(class " + crrTransmit.getClass().getName() + "), just ignored; cause: " + t, t); + } + } + } + return new Snapshot(crrTransmit2Value); + } + + /** + * Restore the backup values from {@link #replay(Capture)} )}/{@link #clear()}. + * + * @param backup the backup values from {@link #replay(Capture)} )}/{@link #clear()} + * @see #replay(Capture) + * @see #clear() + */ + public void restore(@NonNull Backup backup) { + for (Map.Entry, Object> entry : ((Snapshot) backup).crrTransmit2Value.entrySet()) { + CrrTransmit crrTransmit = entry.getKey(); + try { + Object transmitBackup = entry.getValue(); + crrTransmit.restore(transmitBackup); + } catch (Throwable t) { + if (logger.isLoggable(Level.WARNING)) { + logger.log(Level.WARNING, "exception when restore for crrTransmit " + crrTransmit + + "(class " + crrTransmit.getClass().getName() + "), just ignored; cause: " + t, t); + } + } + } + } + + private static class Snapshot implements Capture, Backup { + final HashMap, Object> crrTransmit2Value; + + public Snapshot(HashMap, Object> crrTransmit2Value) { + this.crrTransmit2Value = crrTransmit2Value; + } + } + + + /** + * Register the CrrTransmit. + * + * @param the CrrTransmit capture data type + * @param the CrrTransmit backup data type + * @return true if the input CrrTransmit is not registered + * @see #unregisterCrrTransmit(CrrTransmit) + */ + @SuppressWarnings("unchecked") + public boolean registerCrrTransmit(@NonNull CrrTransmit crrTransmit) { + return crrTransmitSet.add((CrrTransmit) crrTransmit); + } + + /** + * Unregister the CrrTransmit({@code CRR}). + * + * @param the CrrTransmit capture data type + * @param the CrrTransmit backup data type + * @return true if the input crrTransmit is registered + * @see #registerCrrTransmit(CrrTransmit) + */ + @SuppressWarnings("unchecked") + public boolean unregisterCrrTransmit(@NonNull CrrTransmit crrTransmit) { + return crrTransmitSet.remove((CrrTransmit) crrTransmit); + } + + private static final Set> crrTransmitSet = new CopyOnWriteArraySet<>(); +} diff --git a/ttl-core/src/main/java/com/alibaba/ttl3/TransmittableThreadLocal.java b/ttl-core/src/main/java/com/alibaba/ttl3/TransmittableThreadLocal.java new file mode 100644 index 000000000..c88ac10dd --- /dev/null +++ b/ttl-core/src/main/java/com/alibaba/ttl3/TransmittableThreadLocal.java @@ -0,0 +1,397 @@ +package com.alibaba.ttl3; + +import com.alibaba.ttl3.threadpool.TtlExecutors; +import com.alibaba.ttl3.transmitter.Transmittee; +import com.alibaba.ttl3.transmitter.Transmitter; +import edu.umd.cs.findbugs.annotations.NonNull; + +import javax.annotation.ParametersAreNonnullByDefault; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.WeakHashMap; +import java.util.function.Supplier; + +/** + * {@link TransmittableThreadLocal}({@code TTL}) can transmit the value from the thread of submitting task to the thread of executing task. + *

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

+ * ❶ For thread pooling components({@link java.util.concurrent.ThreadPoolExecutor}, + * {@link java.util.concurrent.ForkJoinPool}), Inheritable feature should never happen, + * since threads in thread pooling components is pre-created and pooled, these threads is neutral to biz logic/data. + *
+ * Disable inheritable for thread pooling components by wrapping thread factories using methods + * {@link com.alibaba.ttl3.threadpool.TtlExecutors#getDisableInheritableThreadFactory(java.util.concurrent.ThreadFactory) getDisableInheritableThreadFactory} / + * {@link TtlExecutors#getDefaultDisableInheritableForkJoinWorkerThreadFactory() getDefaultDisableInheritableForkJoinWorkerThreadFactory}. + *
+ * Or you can turn on "disable inheritable for thread pool" by {@link com.alibaba.ttl3.threadpool.agent.TtlAgent} + * so as to wrap thread factories for thread pooling components automatically and transparently. + *

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

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

+ * More discussion about "disable the inheritable ability" + * see + * issue #100: disable Inheritable when it's not necessary and buggy. + * + * @author Jerry Lee (oldratlee at gmail dot com) + * @author Yang Fang (snoop dot fy at gmail dot com) + * @see user guide docs and code repo of TransmittableThreadLocal(TTL) + * @see TtlRunnable + * @see TtlCallable + * @see com.alibaba.ttl3.threadpool.TtlExecutors + * @see com.alibaba.ttl3.threadpool.TtlExecutors#getTtlExecutor(java.util.concurrent.Executor) + * @see com.alibaba.ttl3.threadpool.TtlExecutors#getTtlExecutorService(java.util.concurrent.ExecutorService) + * @see com.alibaba.ttl3.threadpool.TtlExecutors#getTtlScheduledExecutorService(java.util.concurrent.ScheduledExecutorService) + * @see com.alibaba.ttl3.threadpool.TtlExecutors#getDefaultDisableInheritableThreadFactory() + * @see com.alibaba.ttl3.threadpool.TtlExecutors#getDisableInheritableThreadFactory(java.util.concurrent.ThreadFactory) + * @see TtlExecutors#getDefaultDisableInheritableForkJoinWorkerThreadFactory() + * @see TtlExecutors#getDisableInheritableForkJoinWorkerThreadFactory(java.util.concurrent.ForkJoinPool.ForkJoinWorkerThreadFactory) + * @see com.alibaba.ttl3.threadpool.agent.TtlAgent + */ +public class TransmittableThreadLocal extends InheritableThreadLocal { + private final boolean disableIgnoreNullValueSemantics; + + /** + * Default constructor. Create a {@link TransmittableThreadLocal} instance with "Ignore-Null-Value Semantics". + *

+ * About "Ignore-Null-Value Semantics": + * + *

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

+ * This is a pragmatic design decision: + *

    + *
  1. use explicit value type rather than {@code null} value to express biz intent.
  2. + *
  3. safer and more robust code(avoid {@code NPE} risk).
  4. + *
+ *

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

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

+ * More discussion about "Ignore-Null-Value Semantics" see + * Issue #157. + * + * @see #TransmittableThreadLocal(boolean) + */ + public TransmittableThreadLocal() { + this(false); + } + + /** + * Constructor, create a {@link TransmittableThreadLocal} instance + * with parameter {@code disableIgnoreNullValueSemantics} to control "Ignore-Null-Value Semantics". + * + * @param disableIgnoreNullValueSemantics disable "Ignore-Null-Value Semantics" + * @see #TransmittableThreadLocal() + */ + public TransmittableThreadLocal(boolean disableIgnoreNullValueSemantics) { + this.disableIgnoreNullValueSemantics = disableIgnoreNullValueSemantics; + } + + /** + * Creates a transmittable thread local variable. + * The initial value({@link #initialValue()}) of the variable is + * determined by invoking the {@link #get()} method on the {@code Supplier}. + * + * @param the type of the thread local's value + * @param supplier the supplier to be used to determine the initial value + * @return a new transmittable thread local variable + * @throws NullPointerException if the specified supplier is null + * @see #withInitialAndCopier(Supplier, TtlCopier) + */ + @NonNull + @SuppressWarnings("ConstantConditions") + public static TransmittableThreadLocal withInitial(@NonNull Supplier supplier) { + if (supplier == null) throw new NullPointerException("supplier is null"); + + return new SuppliedTransmittableThreadLocal<>(supplier, null, null); + } + + /** + * Creates a transmittable thread local variable. + * The initial value({@link #initialValue()}) of the variable is + * determined by invoking the {@link #get()} method on the {@code Supplier}; + * and the child value({@link #childValue(Object)}) and the transmitting value({@link #transmitteeValue(Object)}) of the variable is + * determined by invoking the {@link TtlCopier#copy(Object)} method on the {@code TtlCopier}. + * + * @param the type of the thread local's value + * @param supplier the supplier to be used to determine the initial value + * @param copierForChildValueAndTransmitteeValue the ttl copier to be used to determine the child value and the transmitting value + * @return a new transmittable thread local variable + * @throws NullPointerException if the specified supplier or copier is null + * @see #withInitial(Supplier) + */ + @NonNull + @ParametersAreNonnullByDefault + @SuppressWarnings("ConstantConditions") + public static TransmittableThreadLocal withInitialAndCopier(Supplier supplier, TtlCopier copierForChildValueAndTransmitteeValue) { + if (supplier == null) throw new NullPointerException("supplier is null"); + if (copierForChildValueAndTransmitteeValue == null) throw new NullPointerException("ttl copier is null"); + + return new SuppliedTransmittableThreadLocal<>(supplier, copierForChildValueAndTransmitteeValue, copierForChildValueAndTransmitteeValue); + } + + /** + * Creates a transmittable thread local variable. + * The initial value({@link #initialValue()}) of the variable is + * determined by invoking the {@link #get()} method on the {@code Supplier}; + * and the child value({@link #childValue(Object)})}) and the transmitting value({@link #transmitteeValue(Object)}) of the variable is + * determined by invoking the {@link TtlCopier#copy(Object)} method on the {@code TtlCopier}. + *

+ * NOTE:
+ * Recommend use {@link #withInitialAndCopier(Supplier, TtlCopier)} instead of this method. + * In most cases, the logic of determining the child value({@link #childValue(Object)}) + * and the transmitting value({@link #transmitteeValue(Object)}) should be the same. + * + * @param the type of the thread local's value + * @param supplier the supplier to be used to determine the initial value + * @param copierForChildValue the ttl copier to be used to determine the child value + * @param copierForTransmitteeValue the ttl copier to be used to determine the transmitting value + * @return a new transmittable thread local variable + * @throws NullPointerException if the specified supplier or copier is null + * @see #withInitial(Supplier) + * @see #withInitialAndCopier(Supplier, TtlCopier) + */ + @NonNull + @ParametersAreNonnullByDefault + @SuppressWarnings("ConstantConditions") + public static TransmittableThreadLocal withInitialAndCopier(Supplier supplier, TtlCopier copierForChildValue, TtlCopier copierForTransmitteeValue) { + if (supplier == null) throw new NullPointerException("supplier is null"); + if (copierForChildValue == null) throw new NullPointerException("ttl copier for child value is null"); + if (copierForTransmitteeValue == null) throw new NullPointerException("ttl copier for copy value is null"); + + return new SuppliedTransmittableThreadLocal<>(supplier, copierForChildValue, copierForTransmitteeValue); + } + + /** + * An extension of ThreadLocal that obtains its initial value from the specified {@code Supplier} + * and obtains its child value and transmitting value from the specified ttl copier. + */ + private static final class SuppliedTransmittableThreadLocal extends TransmittableThreadLocal { + private final Supplier supplier; + private final TtlCopier copierForChildValue; + private final TtlCopier copierForTransmitteeValue; + + SuppliedTransmittableThreadLocal(Supplier supplier, TtlCopier copierForChildValue, TtlCopier copierForTransmitteeValue) { + if (supplier == null) throw new NullPointerException("supplier is null"); + this.supplier = supplier; + this.copierForChildValue = copierForChildValue; + this.copierForTransmitteeValue = copierForTransmitteeValue; + } + + @Override + protected T initialValue() { + return supplier.get(); + } + + @Override + protected T childValue(T parentValue) { + if (copierForChildValue != null) return copierForChildValue.copy(parentValue); + else return super.childValue(parentValue); + } + + @Override + public T transmitteeValue(T parentValue) { + if (copierForTransmitteeValue != null) return copierForTransmitteeValue.copy(parentValue); + else return super.transmitteeValue(parentValue); + } + } + + /** + * Computes the child's initial value for this transmittable thread-local + * variable as a function of the parent's value at the time the child + * thread is created. This method is called from within the parent + * thread before the child is started. + *

+ * Note:
+ * This method is overridden, merely call {@link #transmitteeValue(Object)}; + * and should be overridden if a different behavior is desired. + * + * @param parentValue the parent thread's value + * @return the child thread's initial value + */ + @Override + protected T childValue(T parentValue) { + return transmitteeValue(parentValue); + } + + /** + * Computes the value for this transmittable thread-local variable + * as a function of the source thread's value at the time the task + * Object is created. + *

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

+ * This method merely returns reference of its source thread value(the shadow copy), + * and should be overridden if a different behavior is desired. + */ + protected T transmitteeValue(T parentValue) { + return parentValue; + } + + /** + * {@inheritDoc} + */ + @Override + public final T get() { + T value = super.get(); + if (disableIgnoreNullValueSemantics || null != value) addThisToHolder(); + return value; + } + + /** + * {@inheritDoc} + */ + @Override + public final void set(T value) { + if (!disableIgnoreNullValueSemantics && null == value) { + // may set null to remove value + remove(); + } else { + super.set(value); + addThisToHolder(); + } + } + + /** + * {@inheritDoc} + */ + @Override + public final void remove() { + removeThisFromHolder(); + super.remove(); + } + + private void superRemove() { + super.remove(); + } + + private T getTransmitteeValue() { + return transmitteeValue(get()); + } + + // Note about the holder: + // 1. holder self is a InheritableThreadLocal(a *ThreadLocal*). + // 2. The type of value in the holder is WeakHashMap, ?>. + // 2.1 but the WeakHashMap is used as a *Set*: + // the value of WeakHashMap is *always* null, and never used. + // 2.2 WeakHashMap support *null* value. + private static final InheritableThreadLocal, ?>> holder = + new InheritableThreadLocal, ?>>() { + @Override + protected WeakHashMap, ?> initialValue() { + return new WeakHashMap<>(); + } + + @Override + protected WeakHashMap, ?> childValue(WeakHashMap, ?> parentValue) { + return new WeakHashMap, Object>(parentValue); + } + }; + + @SuppressWarnings("unchecked") + private void addThisToHolder() { + if (!holder.get().containsKey(this)) { + holder.get().put((TransmittableThreadLocal) this, null); // WeakHashMap supports null value. + } + } + + private void removeThisFromHolder() { + holder.get().remove(this); + } + + + private static class TtlTransmittee implements Transmittee, Object>, HashMap, Object>> { + @NonNull + @Override + public HashMap, Object> capture() { + final HashMap, Object> ttl2Value = new HashMap<>(holder.get().size()); + for (TransmittableThreadLocal threadLocal : holder.get().keySet()) { + ttl2Value.put(threadLocal, threadLocal.getTransmitteeValue()); + } + return ttl2Value; + } + + @NonNull + @Override + public HashMap, Object> replay(@NonNull HashMap, Object> captured) { + final HashMap, Object> backup = new HashMap<>(holder.get().size()); + + for (final Iterator> iterator = holder.get().keySet().iterator(); iterator.hasNext(); ) { + TransmittableThreadLocal threadLocal = iterator.next(); + + // backup + backup.put(threadLocal, threadLocal.get()); + + // clear the TTL values that is not in captured + // avoid the extra TTL values after replay when run task + if (!captured.containsKey(threadLocal)) { + iterator.remove(); + threadLocal.superRemove(); + } + } + + // set TTL values to captured + setTtlValuesTo(captured); + + return backup; + } + + @NonNull + @Override + public HashMap, Object> clear() { + return replay(new HashMap<>(0)); + } + + @Override + public void restore(@NonNull HashMap, Object> backup) { + for (final Iterator> iterator = holder.get().keySet().iterator(); iterator.hasNext(); ) { + TransmittableThreadLocal threadLocal = iterator.next(); + + // clear the TTL values that is not in backup + // avoid the extra TTL values after restore + if (!backup.containsKey(threadLocal)) { + iterator.remove(); + threadLocal.superRemove(); + } + } + + // restore TTL values + setTtlValuesTo(backup); + } + + private static void setTtlValuesTo(@NonNull HashMap, Object> ttlValues) { + for (Map.Entry, Object> entry : ttlValues.entrySet()) { + TransmittableThreadLocal threadLocal = entry.getKey(); + threadLocal.set(entry.getValue()); + } + } + } + + private static final TtlTransmittee ttlTransmittee = new TtlTransmittee(); + + static { + Transmitter.registerTransmittee(ttlTransmittee); + } +} diff --git a/ttl-core/src/main/java/com/alibaba/ttl3/TtlCallable.java b/ttl-core/src/main/java/com/alibaba/ttl3/TtlCallable.java new file mode 100644 index 000000000..af49537b0 --- /dev/null +++ b/ttl-core/src/main/java/com/alibaba/ttl3/TtlCallable.java @@ -0,0 +1,265 @@ +package com.alibaba.ttl3; + +import com.alibaba.crr.composite.Backup; +import com.alibaba.crr.composite.Capture; +import com.alibaba.ttl3.spi.TtlAttachments; +import com.alibaba.ttl3.spi.TtlAttachmentsDelegate; +import com.alibaba.ttl3.spi.TtlEnhanced; +import com.alibaba.ttl3.spi.TtlWrapper; +import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import org.jetbrains.annotations.Contract; + +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.alibaba.ttl3.transmitter.Transmitter.*; + +/** + * {@link TtlCallable} decorate {@link Callable} to get {@link TransmittableThreadLocal} value + * and transmit it to the time of {@link Callable} execution, needed when use {@link Callable} to thread pool. + *

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

+ * Other TTL Wrapper for common {@code Functional Interface} see {@link TtlWrappers}. + * + * @author Jerry Lee (oldratlee at gmail dot com) + * @see com.alibaba.ttl3.threadpool.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 + */ +public final class TtlCallable implements Callable, TtlWrapper>, TtlEnhanced, TtlAttachments { + private final AtomicReference capturedRef; + private final Callable callable; + private final boolean releaseTtlValueReferenceAfterCall; + + private TtlCallable(@NonNull Callable callable, boolean releaseTtlValueReferenceAfterCall) { + this.capturedRef = new AtomicReference<>(capture()); + this.callable = callable; + this.releaseTtlValueReferenceAfterCall = releaseTtlValueReferenceAfterCall; + } + + /** + * wrap method {@link Callable#call()}. + */ + @Override + @SuppressFBWarnings("THROWS_METHOD_THROWS_CLAUSE_BASIC_EXCEPTION") + public V call() throws Exception { + final Capture captured = capturedRef.get(); + if (captured == null || releaseTtlValueReferenceAfterCall && !capturedRef.compareAndSet(captured, null)) { + throw new IllegalStateException("TTL value reference is released after call!"); + } + + final Backup backup = replay(captured); + try { + return callable.call(); + } finally { + restore(backup); + } + } + + /** + * return the original/underneath {@link Callable}. + */ + @NonNull + public Callable getCallable() { + return unwrap(); + } + + /** + * unwrap to the original/underneath {@link Callable}. + * + * @see TtlWrappers#unwrap(Object) + */ + @NonNull + @Override + public Callable unwrap() { + return callable; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + TtlCallable that = (TtlCallable) o; + + return callable.equals(that.callable); + } + + @Override + public int hashCode() { + return callable.hashCode(); + } + + @Override + public String toString() { + return this.getClass().getName() + " - " + callable.toString(); + } + + /** + * Factory method, wrap input {@link Callable} to {@link TtlCallable}. + *

+ * This method is idempotent. + * + * @param callable input {@link Callable} + * @return Wrapped {@link Callable} + */ + @Nullable + @Contract(value = "null -> null; !null -> !null", pure = true) + public static TtlCallable get(@Nullable Callable callable) { + return get(callable, false, false); + } + + + /** + * Factory method, wrap input {@link Callable} to {@link TtlCallable}. + *

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

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

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

+ * so {@code TtlCallable.unwrap(TtlCallable.get(callable))} will always return the same input {@code callable} object. + * + * @see #get(Callable) + * @see TtlWrappers#unwrap(Object) + */ + @Nullable + @Contract(value = "null -> null; !null -> !null", pure = true) + public static Callable unwrap(@Nullable Callable callable) { + if (!(callable instanceof TtlCallable)) return callable; + else return ((TtlCallable) callable).getCallable(); + } + + /** + * Unwrap {@link TtlCallable} to the original/underneath one. + *

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

+ * This method is {@code null}-safe, when input {@code Callable} collection parameter is {@code null}, return an empty list. + * + * @see #gets(Collection) + * @see #unwrap(Callable) + */ + @NonNull + public static List> unwraps(@Nullable Collection> tasks) { + if (null == tasks) return Collections.emptyList(); + + List> copy = new ArrayList<>(); + for (Callable task : tasks) { + if (!(task instanceof TtlCallable)) copy.add(task); + else copy.add(((TtlCallable) task).getCallable()); + } + return copy; + } + + private final TtlAttachmentsDelegate ttlAttachment = new TtlAttachmentsDelegate(); + + /** + * see {@link TtlAttachments#setTtlAttachment(String, Object)} + * + */ + @Override + public void setTtlAttachment(@NonNull String key, Object value) { + ttlAttachment.setTtlAttachment(key, value); + } + + /** + * see {@link TtlAttachments#getTtlAttachment(String)} + * + */ + @Override + public T getTtlAttachment(@NonNull String key) { + return ttlAttachment.getTtlAttachment(key); + } +} diff --git a/ttl-core/src/main/java/com/alibaba/ttl3/TtlCopier.java b/ttl-core/src/main/java/com/alibaba/ttl3/TtlCopier.java new file mode 100644 index 000000000..0cf05b264 --- /dev/null +++ b/ttl-core/src/main/java/com/alibaba/ttl3/TtlCopier.java @@ -0,0 +1,18 @@ +package com.alibaba.ttl3; + +import java.util.function.Supplier; + +/** + * {@code TtlCopier} create the copy value. + * + * @author Jerry Lee (oldratlee at gmail dot com) + * @see TransmittableThreadLocal#withInitialAndCopier(Supplier, TtlCopier) + * @see com.alibaba.ttl3.transmitter.ThreadLocalTransmitRegister#registerThreadLocal(ThreadLocal, TtlCopier) + */ +@FunctionalInterface +public interface TtlCopier { + /** + * the copy value logic. + */ + T copy(T parentValue); +} diff --git a/ttl-core/src/main/java/com/alibaba/ttl3/TtlRecursiveAction.java b/ttl-core/src/main/java/com/alibaba/ttl3/TtlRecursiveAction.java new file mode 100644 index 000000000..d6851f425 --- /dev/null +++ b/ttl-core/src/main/java/com/alibaba/ttl3/TtlRecursiveAction.java @@ -0,0 +1,62 @@ +package com.alibaba.ttl3; + +import com.alibaba.crr.composite.Backup; +import com.alibaba.crr.composite.Capture; +import com.alibaba.ttl3.spi.TtlEnhanced; + +import java.util.concurrent.ForkJoinTask; + +import static com.alibaba.ttl3.transmitter.Transmitter.*; + +/** + * A recursive resultless {@link ForkJoinTask} enhanced by {@link TransmittableThreadLocal}. + *

+ * Recommend to use {@link com.alibaba.ttl3.threadpool.agent.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 com.alibaba.ttl3.threadpool.TtlExecutors + * @see com.alibaba.ttl3.threadpool.agent.TtlAgent + */ +public abstract class TtlRecursiveAction extends ForkJoinTask implements TtlEnhanced { + + private static final long serialVersionUID = -5753568484583412377L; + + private final Capture 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() { + final Backup backup = replay(captured); + try { + compute(); + return true; + } finally { + restore(backup); + } + } +} diff --git a/ttl-core/src/main/java/com/alibaba/ttl3/TtlRecursiveTask.java b/ttl-core/src/main/java/com/alibaba/ttl3/TtlRecursiveTask.java new file mode 100644 index 000000000..afaf35c76 --- /dev/null +++ b/ttl-core/src/main/java/com/alibaba/ttl3/TtlRecursiveTask.java @@ -0,0 +1,71 @@ +package com.alibaba.ttl3; + +import com.alibaba.crr.composite.Backup; +import com.alibaba.crr.composite.Capture; +import com.alibaba.ttl3.spi.TtlEnhanced; + +import java.util.concurrent.ForkJoinTask; + +import static com.alibaba.ttl3.transmitter.Transmitter.*; + +/** + * A recursive result-bearing {@link ForkJoinTask} enhanced by {@link TransmittableThreadLocal}. + *

+ * Recommend to use {@link com.alibaba.ttl3.threadpool.agent.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 com.alibaba.ttl3.threadpool.TtlExecutors + * @see com.alibaba.ttl3.threadpool.agent.TtlAgent + */ +public abstract class TtlRecursiveTask extends ForkJoinTask implements TtlEnhanced { + + private static final long serialVersionUID = 1814679366926362436L; + + private final Capture 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() { + final Backup backup = replay(captured); + try { + result = compute(); + return true; + } finally { + restore(backup); + } + } + +} diff --git a/ttl-core/src/main/java/com/alibaba/ttl3/TtlRunnable.java b/ttl-core/src/main/java/com/alibaba/ttl3/TtlRunnable.java new file mode 100644 index 000000000..d69d1b44e --- /dev/null +++ b/ttl-core/src/main/java/com/alibaba/ttl3/TtlRunnable.java @@ -0,0 +1,263 @@ +package com.alibaba.ttl3; + +import com.alibaba.crr.composite.Backup; +import com.alibaba.crr.composite.Capture; +import com.alibaba.ttl3.spi.TtlAttachments; +import com.alibaba.ttl3.spi.TtlAttachmentsDelegate; +import com.alibaba.ttl3.spi.TtlEnhanced; +import com.alibaba.ttl3.spi.TtlWrapper; +import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; +import org.jetbrains.annotations.Contract; + +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.alibaba.ttl3.transmitter.Transmitter.*; + +/** + * {@link TtlRunnable} decorate {@link Runnable} to get {@link TransmittableThreadLocal} value + * and transmit it to the time of {@link Runnable} execution, needed when use {@link Runnable} to thread pool. + *

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

+ * Other TTL Wrapper for common {@code Functional Interface} see {@link TtlWrappers}. + * + * @author Jerry Lee (oldratlee at gmail dot com) + * @see com.alibaba.ttl3.threadpool.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 + */ +public final class TtlRunnable implements Runnable, TtlWrapper, TtlEnhanced, TtlAttachments { + private final AtomicReference capturedRef; + private final Runnable runnable; + private final boolean releaseTtlValueReferenceAfterRun; + + private TtlRunnable(@NonNull Runnable runnable, boolean releaseTtlValueReferenceAfterRun) { + this.capturedRef = new AtomicReference<>(capture()); + this.runnable = runnable; + this.releaseTtlValueReferenceAfterRun = releaseTtlValueReferenceAfterRun; + } + + /** + * wrap method {@link Runnable#run()}. + */ + @Override + public void run() { + final Capture captured = capturedRef.get(); + if (captured == null || releaseTtlValueReferenceAfterRun && !capturedRef.compareAndSet(captured, null)) { + throw new IllegalStateException("TTL value reference is released after run!"); + } + + final Backup backup = replay(captured); + try { + runnable.run(); + } finally { + restore(backup); + } + } + + /** + * return original/unwrapped {@link Runnable}. + */ + @NonNull + public Runnable getRunnable() { + return unwrap(); + } + + /** + * unwrap to original/unwrapped {@link Runnable}. + * + * @see TtlWrappers#unwrap(Object) + */ + @NonNull + @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 + @Contract(value = "null -> null; !null -> !null", pure = true) + 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 + @Contract(value = "null, _ -> null; !null, _ -> !null", pure = true) + public static TtlRunnable get(@Nullable Runnable runnable, boolean releaseTtlValueReferenceAfterRun) { + return get(runnable, releaseTtlValueReferenceAfterRun, false); + } + + /** + * Factory method, wrap input {@link Runnable} to {@link TtlRunnable}. + * + * @param runnable input {@link Runnable}. if input is {@code null}, return {@code null}. + * @param releaseTtlValueReferenceAfterRun release TTL value reference after run, avoid memory leak even if {@link TtlRunnable} is referred. + * @param idempotent is idempotent mode or not. if {@code true}, just return input {@link Runnable} when it's {@link TtlRunnable}, + * otherwise throw {@link IllegalStateException}. + * Caution: {@code true} will cover up bugs! DO NOT set, only when you know why. + * @return Wrapped {@link Runnable} + * @throws IllegalStateException when input is {@link TtlRunnable} already and not idempotent. + */ + @Nullable + @Contract(value = "null, _, _ -> null; !null, _, _ -> !null", pure = true) + 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. + */ + @NonNull + public static List gets(@Nullable Collection tasks) { + return gets(tasks, false, false); + } + + /** + * wrap input {@link Runnable} Collection to {@link TtlRunnable} Collection. + * + * @param tasks task to be wrapped. if input is {@code null}, return {@code null}. + * @param releaseTtlValueReferenceAfterRun release TTL value reference after run, avoid memory leak even if {@link TtlRunnable} is referred. + * @return wrapped tasks + * @throws IllegalStateException when input is {@link TtlRunnable} already. + */ + @NonNull + public static List gets(@Nullable Collection tasks, boolean releaseTtlValueReferenceAfterRun) { + return gets(tasks, releaseTtlValueReferenceAfterRun, false); + } + + /** + * wrap input {@link Runnable} Collection to {@link TtlRunnable} Collection. + * + * @param tasks task to be wrapped. if input is {@code null}, return {@code null}. + * @param releaseTtlValueReferenceAfterRun release TTL value reference after run, avoid memory leak even if {@link TtlRunnable} is referred. + * @param idempotent is idempotent mode or not. if {@code true}, just return input {@link Runnable} when it's {@link TtlRunnable}, + * otherwise throw {@link IllegalStateException}. + * Caution: {@code true} will cover up bugs! DO NOT set, only when you know why. + * @return wrapped tasks + * @throws IllegalStateException when input is {@link TtlRunnable} already and not idempotent. + */ + @NonNull + public static List gets(@Nullable Collection tasks, boolean releaseTtlValueReferenceAfterRun, boolean idempotent) { + if (null == tasks) return Collections.emptyList(); + + List copy = new ArrayList<>(); + for (Runnable task : tasks) { + copy.add(TtlRunnable.get(task, releaseTtlValueReferenceAfterRun, idempotent)); + } + return copy; + } + + /** + * Unwrap {@link TtlRunnable} to the original/underneath one. + *

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

+ * so {@code TtlRunnable.unwrap(TtlRunnable.get(runnable))} will always return the same input {@code runnable} object. + * + * @see #get(Runnable) + * @see TtlWrappers#unwrap(Object) + */ + @Nullable + @Contract(value = "null -> null; !null -> !null", pure = true) + public static Runnable unwrap(@Nullable Runnable runnable) { + if (!(runnable instanceof TtlRunnable)) return runnable; + else return ((TtlRunnable) runnable).getRunnable(); + } + + /** + * Unwrap {@link TtlRunnable} to the original/underneath one for collection. + *

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

+ * This method is {@code null}-safe, when input {@code Runnable} parameter collection is {@code null}, return a empty list. + * + * @see #gets(Collection) + * @see #unwrap(Runnable) + */ + @NonNull + public static List unwraps(@Nullable Collection tasks) { + if (null == tasks) return Collections.emptyList(); + + List copy = new ArrayList<>(); + for (Runnable task : tasks) { + if (!(task instanceof TtlRunnable)) copy.add(task); + else copy.add(((TtlRunnable) task).getRunnable()); + } + return copy; + } + + private final TtlAttachmentsDelegate ttlAttachment = new TtlAttachmentsDelegate(); + + /** + * see {@link TtlAttachments#setTtlAttachment(String, Object)} + * + */ + @Override + public void setTtlAttachment(@NonNull String key, Object value) { + ttlAttachment.setTtlAttachment(key, value); + } + + /** + * see {@link TtlAttachments#getTtlAttachment(String)} + * + */ + @Override + public T getTtlAttachment(@NonNull String key) { + return ttlAttachment.getTtlAttachment(key); + } +} diff --git a/ttl-core/src/main/java/com/alibaba/ttl3/TtlTimerTask.java b/ttl-core/src/main/java/com/alibaba/ttl3/TtlTimerTask.java new file mode 100644 index 000000000..d9d6a93d6 --- /dev/null +++ b/ttl-core/src/main/java/com/alibaba/ttl3/TtlTimerTask.java @@ -0,0 +1,195 @@ +package com.alibaba.ttl3; + +import com.alibaba.crr.composite.Backup; +import com.alibaba.crr.composite.Capture; +import com.alibaba.ttl3.spi.TtlEnhanced; +import com.alibaba.ttl3.spi.TtlWrapper; +import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; +import org.jetbrains.annotations.Contract; + +import java.util.*; +import java.util.concurrent.atomic.AtomicReference; + +import static com.alibaba.ttl3.transmitter.Transmitter.*; + +/** + * {@link TtlTimerTask} decorate {@link TimerTask} to get {@link TransmittableThreadLocal} value + * and transmit it to the time of {@link TtlTimerTask} execution, needed when use {@link TtlTimerTask} to {@link TimerTask}. + *

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

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

+ * This method is idempotent. + * + * @param timerTask input {@link TimerTask} + * @return Wrapped {@link TimerTask} + */ + @Nullable + @Contract(value = "null -> null; !null -> !null", pure = true) + public static TtlTimerTask get(@Nullable TimerTask timerTask) { + return get(timerTask, false, false); + } + + /** + * Factory method, wrap input {@link TimerTask} to {@link TtlTimerTask}. + *

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

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

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

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

+ * This method is {@code null}-safe, when input {@code TimerTask} parameter is {@code null}, return a empty list. + * + * @see #unwrap(TimerTask) + */ + @NonNull + public static List unwraps(@Nullable Collection tasks) { + if (null == tasks) return Collections.emptyList(); + + List copy = new ArrayList<>(); + for (TimerTask task : tasks) { + if (!(task instanceof TtlTimerTask)) copy.add(task); + else copy.add(((TtlTimerTask) task).getTimerTask()); + } + return copy; + } +} diff --git a/ttl-core/src/main/java/com/alibaba/ttl3/TtlWrappers.java b/ttl-core/src/main/java/com/alibaba/ttl3/TtlWrappers.java new file mode 100644 index 000000000..aed71472f --- /dev/null +++ b/ttl-core/src/main/java/com/alibaba/ttl3/TtlWrappers.java @@ -0,0 +1,386 @@ +package com.alibaba.ttl3; + +import com.alibaba.crr.composite.Backup; +import com.alibaba.crr.composite.Capture; +import com.alibaba.ttl3.spi.TtlEnhanced; +import com.alibaba.ttl3.spi.TtlWrapper; +import com.alibaba.ttl3.threadpool.TtlExecutors; +import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; +import org.jetbrains.annotations.Contract; + +import java.util.function.*; + +import static com.alibaba.ttl3.transmitter.Transmitter.*; + +/** + * Util methods for TTL Wrapper. + * + *

    + *
  • wrap common {@code Functional Interface}.
  • + *
  • unwrap TTL Wrapper and check TTL Wrapper.
  • + *
+ *

+ * Note: + *

    + *
  • all methods is {@code null}-safe, when input parameter is {@code null}, return {@code null}.
  • + *
  • all wrap method skip wrap (aka. just return input parameter), when input parameter is already wrapped.
  • + *
+ * + * @author Jerry Lee (oldratlee at gmail dot com) + * @author huangfei1101 (fei.hf at alibaba-inc dot com) + * @see TtlRunnable + * @see TtlCallable + * @see TtlWrapper + */ +public final class TtlWrappers { + /** + * wrap {@link Supplier} to TTL wrapper. + * + * @param supplier input {@link Supplier} + * @return Wrapped {@link Supplier} + * @see TtlWrappers#unwrap(Object) + */ + @Nullable + @Contract(value = "null -> null; !null -> !null", pure = true) + public static Supplier wrapSupplier(@Nullable Supplier supplier) { + if (supplier == null) return null; + else if (supplier instanceof TtlEnhanced) return supplier; + else return new TtlSupplier<>(supplier); + } + + /** + * Generic unwrap method, unwrap {@link TtlWrapper} to the original/underneath one. + *

+ * this method is {@code null}-safe, when input parameter is {@code null}, return {@code null}; + * if input parameter is not a {@link TtlWrapper} just return input. + * + * @see TtlRunnable#unwrap(Runnable) + * @see TtlCallable#unwrap(java.util.concurrent.Callable) + * @see com.alibaba.ttl3.threadpool.TtlExecutors#unwrapExecutor(java.util.concurrent.Executor) + * @see com.alibaba.ttl3.threadpool.TtlExecutors#unwrapThreadFactory(java.util.concurrent.ThreadFactory) + * @see com.alibaba.ttl3.threadpool.TtlExecutors#unwrapComparator(java.util.Comparator) + * @see TtlExecutors#unwrapForkJoinWorkerThreadFactory(java.util.concurrent.ForkJoinPool.ForkJoinWorkerThreadFactory) + * @see TtlWrappers#wrapSupplier(Supplier) + * @see TtlWrappers#wrapConsumer(Consumer) + * @see TtlWrappers#wrapBiConsumer(BiConsumer) + * @see TtlWrappers#wrapFunction(Function) + * @see TtlWrappers#wrapBiFunction(BiFunction) + * @see #isWrapper(Object) + */ + @Nullable + @Contract(value = "null -> null; !null -> !null", pure = true) + @SuppressWarnings("unchecked") + public static T unwrap(@Nullable T obj) { + if (!isWrapper(obj)) return obj; + else return ((TtlWrapper) obj).unwrap(); + } + + /** + * check the input object is a {@code TtlWrapper} or not. + * + * @see #unwrap(Object) + */ + public static boolean isWrapper(@Nullable T obj) { + return obj instanceof TtlWrapper; + } + + private static class TtlSupplier implements Supplier, TtlWrapper>, TtlEnhanced { + final Supplier supplier; + final Capture captured; + + TtlSupplier(@NonNull Supplier supplier) { + this.supplier = supplier; + this.captured = capture(); + } + + @Override + public T get() { + final Backup backup = replay(captured); + try { + return supplier.get(); + } finally { + restore(backup); + } + } + + @NonNull + @Override + public Supplier unwrap() { + return supplier; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + TtlSupplier that = (TtlSupplier) o; + + return supplier.equals(that.supplier); + } + + @Override + public int hashCode() { + return supplier.hashCode(); + } + + @Override + public String toString() { + return this.getClass().getName() + " - " + supplier.toString(); + } + } + + + /** + * wrap {@link Consumer} to TTL wrapper. + * + * @param consumer input {@link Consumer} + * @return Wrapped {@link Consumer} + * @see TtlWrappers#unwrap(Object) + */ + @Nullable + @Contract(value = "null -> null; !null -> !null", pure = true) + public static Consumer wrapConsumer(@Nullable Consumer consumer) { + if (consumer == null) return null; + else if (consumer instanceof TtlEnhanced) return consumer; + else return new TtlConsumer<>(consumer); + } + + private static class TtlConsumer implements Consumer, TtlWrapper>, TtlEnhanced { + final Consumer consumer; + final Capture captured; + + TtlConsumer(@NonNull Consumer consumer) { + this.consumer = consumer; + this.captured = capture(); + } + + @Override + public void accept(T t) { + final Backup backup = replay(captured); + try { + consumer.accept(t); + } finally { + restore(backup); + } + } + + @NonNull + @Override + public Consumer unwrap() { + return consumer; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + TtlConsumer that = (TtlConsumer) o; + + return consumer.equals(that.consumer); + } + + @Override + public int hashCode() { + return consumer.hashCode(); + } + + @Override + public String toString() { + return this.getClass().getName() + " - " + consumer.toString(); + } + } + + + /** + * wrap {@link BiConsumer} to TTL wrapper. + * + * @param consumer input {@link BiConsumer} + * @return Wrapped {@link BiConsumer} + * @see TtlWrappers#unwrap(Object) + */ + @Nullable + @Contract(value = "null -> null; !null -> !null", pure = true) + public static BiConsumer wrapBiConsumer(@Nullable BiConsumer consumer) { + if (consumer == null) return null; + else if (consumer instanceof TtlEnhanced) return consumer; + else return new TtlBiConsumer<>(consumer); + } + + private static class TtlBiConsumer implements BiConsumer, TtlWrapper>, TtlEnhanced { + final BiConsumer consumer; + final Capture captured; + + TtlBiConsumer(@NonNull BiConsumer consumer) { + this.consumer = consumer; + this.captured = capture(); + } + + @Override + public void accept(T t, U u) { + final Backup backup = replay(captured); + try { + consumer.accept(t, u); + } finally { + restore(backup); + } + } + + @NonNull + @Override + public BiConsumer unwrap() { + return consumer; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + TtlBiConsumer that = (TtlBiConsumer) o; + + return consumer.equals(that.consumer); + } + + @Override + public int hashCode() { + return consumer.hashCode(); + } + + @Override + public String toString() { + return this.getClass().getName() + " - " + consumer.toString(); + } + } + + + /** + * wrap {@link Function} to TTL wrapper. + * + * @param fn input {@link Function} + * @return Wrapped {@link Function} + * @see TtlWrappers#unwrap(Object) + */ + @Nullable + @Contract(value = "null -> null; !null -> !null", pure = true) + public static Function wrapFunction(@Nullable Function fn) { + if (fn == null) return null; + else if (fn instanceof TtlEnhanced) return fn; + else return new TtlFunction<>(fn); + } + + private static class TtlFunction implements Function, TtlWrapper>, TtlEnhanced { + final Function fn; + final Capture captured; + + TtlFunction(@NonNull Function fn) { + this.fn = fn; + this.captured = capture(); + } + + @Override + public R apply(T t) { + final Backup backup = replay(captured); + try { + return fn.apply(t); + } finally { + restore(backup); + } + } + + @NonNull + @Override + public Function unwrap() { + return fn; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + TtlFunction that = (TtlFunction) o; + + return fn.equals(that.fn); + } + + @Override + public int hashCode() { + return fn.hashCode(); + } + + @Override + public String toString() { + return this.getClass().getName() + " - " + fn.toString(); + } + } + + + /** + * wrap {@link BiFunction} to TTL wrapper. + * + * @param fn input {@link BiFunction} + * @return Wrapped {@link BiFunction} + * @see TtlWrappers#unwrap(Object) + */ + @Nullable + @Contract(value = "null -> null; !null -> !null", pure = true) + public static BiFunction wrapBiFunction(@Nullable BiFunction fn) { + if (fn == null) return null; + else if (fn instanceof TtlEnhanced) return fn; + else return new TtlBiFunction<>(fn); + } + + private static class TtlBiFunction implements BiFunction, TtlWrapper>, TtlEnhanced { + final BiFunction fn; + final Capture captured; + + TtlBiFunction(@NonNull BiFunction fn) { + this.fn = fn; + this.captured = capture(); + } + + @Override + public R apply(T t, U u) { + final Backup backup = replay(captured); + try { + return fn.apply(t, u); + } finally { + restore(backup); + } + } + + @NonNull + @Override + public BiFunction unwrap() { + return fn; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + TtlBiFunction that = (TtlBiFunction) o; + + return fn.equals(that.fn); + } + + @Override + public int hashCode() { + return fn.hashCode(); + } + + @Override + public String toString() { + return this.getClass().getName() + " - " + fn.toString(); + } + } + + + private TtlWrappers() { + throw new InstantiationError("Must not instantiate this class"); + } +} diff --git a/ttl-core/src/main/java/com/alibaba/ttl3/package-info.java b/ttl-core/src/main/java/com/alibaba/ttl3/package-info.java new file mode 100644 index 000000000..23e5fee7d --- /dev/null +++ b/ttl-core/src/main/java/com/alibaba/ttl3/package-info.java @@ -0,0 +1,10 @@ +/** + * TTL API. + * + * @author Jerry Lee (oldratlee at gmail dot com) + * @see com.alibaba.ttl3.TransmittableThreadLocal + * @see com.alibaba.ttl3.TtlRunnable + * @see com.alibaba.ttl3.TtlCallable + * @see com.alibaba.ttl3.TtlWrappers + */ +package com.alibaba.ttl3; diff --git a/ttl-core/src/main/java/com/alibaba/ttl3/spi/TtlAttachments.java b/ttl-core/src/main/java/com/alibaba/ttl3/spi/TtlAttachments.java new file mode 100644 index 000000000..0a2a212ed --- /dev/null +++ b/ttl-core/src/main/java/com/alibaba/ttl3/spi/TtlAttachments.java @@ -0,0 +1,34 @@ +package com.alibaba.ttl3.spi; + +import edu.umd.cs.findbugs.annotations.NonNull; + +/** + * The TTL attachments for TTL tasks, + * eg: {@link com.alibaba.ttl3.TtlRunnable}, {@link com.alibaba.ttl3.TtlCallable}. + * + * @author Jerry Lee (oldratlee at gmail dot com) + */ +public interface TtlAttachments extends TtlEnhanced { + /** + * set the TTL attachments for TTL tasks + * + * @param key attachment key + * @param value attachment value + */ + void setTtlAttachment(@NonNull String key, Object value); + + /** + * get the TTL attachment for TTL tasks + * + * @param key attachment key + */ + T getTtlAttachment(@NonNull String key); + + /** + * The attachment key of TTL task, weather this task is a auto wrapper task. + *

+ * so the value of this attachment is a {@code boolean}. + * + */ + String KEY_IS_AUTO_WRAPPER = "ttl.is.auto.wrapper"; +} diff --git a/ttl-core/src/main/java/com/alibaba/ttl3/spi/TtlAttachmentsDelegate.java b/ttl-core/src/main/java/com/alibaba/ttl3/spi/TtlAttachmentsDelegate.java new file mode 100644 index 000000000..959d49ad6 --- /dev/null +++ b/ttl-core/src/main/java/com/alibaba/ttl3/spi/TtlAttachmentsDelegate.java @@ -0,0 +1,62 @@ +package com.alibaba.ttl3.spi; + +import com.alibaba.ttl3.TtlWrappers; +import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; + +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +/** + * {@link TtlAttachments} delegate/implementation. + * + * @author Jerry Lee (oldratlee at gmail dot com) + * @see com.alibaba.ttl3.TtlRunnable + * @see com.alibaba.ttl3.TtlCallable + */ +public class TtlAttachmentsDelegate implements TtlAttachments { + private final ConcurrentMap attachments = new ConcurrentHashMap<>(); + + @Override + public void setTtlAttachment(@NonNull String key, Object value) { + attachments.put(key, value); + } + + @Override + @SuppressWarnings("unchecked") + public T getTtlAttachment(@NonNull String key) { + return (T) attachments.get(key); + } + + // ======== AutoWrapper Util Methods ======== + + /** + * @see TtlAttachments#KEY_IS_AUTO_WRAPPER + */ + public static boolean isAutoWrapper(@Nullable Object ttlAttachments) { + if (!(ttlAttachments instanceof TtlAttachments)) return false; + + final Boolean value = ((TtlAttachments) ttlAttachments).getTtlAttachment(KEY_IS_AUTO_WRAPPER); + if (value == null) return false; + + return value; + } + + /** + * @see TtlAttachments#KEY_IS_AUTO_WRAPPER + */ + public static void setAutoWrapperAttachment(@Nullable Object ttlAttachment) { + if (!(ttlAttachment instanceof TtlAttachments)) return; + + ((TtlAttachments) ttlAttachment).setTtlAttachment(TtlAttachments.KEY_IS_AUTO_WRAPPER, true); + } + + /** + * @see TtlAttachments#KEY_IS_AUTO_WRAPPER + */ + @Nullable + public static T unwrapIfIsAutoWrapper(@Nullable T obj) { + if (isAutoWrapper(obj)) return TtlWrappers.unwrap(obj); + else return obj; + } +} diff --git a/ttl-core/src/main/java/com/alibaba/ttl3/spi/TtlEnhanced.java b/ttl-core/src/main/java/com/alibaba/ttl3/spi/TtlEnhanced.java new file mode 100644 index 000000000..4942ead5a --- /dev/null +++ b/ttl-core/src/main/java/com/alibaba/ttl3/spi/TtlEnhanced.java @@ -0,0 +1,15 @@ +package com.alibaba.ttl3.spi; + +/** + * a Ttl marker/tag interface, for ttl enhanced class, for example {@code TTL wrapper} + * like {@link com.alibaba.ttl3.TtlRunnable}, {@link com.alibaba.ttl3.TtlCallable}. + * + * @author Jerry Lee (oldratlee at gmail dot com) + * @see com.alibaba.ttl3.TtlRunnable + * @see com.alibaba.ttl3.TtlCallable + * @see com.alibaba.ttl3.TtlRecursiveAction + * @see com.alibaba.ttl3.TtlRecursiveTask + * @see TtlAttachments + */ +public interface TtlEnhanced { +} diff --git a/ttl-core/src/main/java/com/alibaba/ttl3/spi/TtlWrapper.java b/ttl-core/src/main/java/com/alibaba/ttl3/spi/TtlWrapper.java new file mode 100644 index 000000000..421de7292 --- /dev/null +++ b/ttl-core/src/main/java/com/alibaba/ttl3/spi/TtlWrapper.java @@ -0,0 +1,34 @@ +package com.alibaba.ttl3.spi; + +import com.alibaba.ttl3.threadpool.DisableInheritableForkJoinWorkerThreadFactory; +import com.alibaba.ttl3.threadpool.DisableInheritableThreadFactory; +import com.alibaba.ttl3.threadpool.TtlExecutors; +import edu.umd.cs.findbugs.annotations.NonNull; + +/** + * Ttl Wrapper interface. + *

+ * Used to mark wrapper types, for example: + *

    + *
  • {@link com.alibaba.ttl3.TtlCallable}
  • + *
  • {@link TtlExecutors}
  • + *
  • {@link DisableInheritableThreadFactory}
  • + *
+ * + * @author Jerry Lee (oldratlee at gmail dot com) + * @see com.alibaba.ttl3.TtlWrappers#unwrap + * @see com.alibaba.ttl3.TtlCallable + * @see com.alibaba.ttl3.TtlRunnable + * @see TtlExecutors + * @see DisableInheritableThreadFactory + * @see DisableInheritableForkJoinWorkerThreadFactory + */ +public interface TtlWrapper extends TtlEnhanced { + /** + * unwrap {@link TtlWrapper} to the original/underneath one. + * + * @see com.alibaba.ttl3.TtlWrappers#unwrap(Object) + */ + @NonNull + T unwrap(); +} diff --git a/ttl-core/src/main/java/com/alibaba/ttl3/spi/package-info.java b/ttl-core/src/main/java/com/alibaba/ttl3/spi/package-info.java new file mode 100644 index 000000000..cb80eba82 --- /dev/null +++ b/ttl-core/src/main/java/com/alibaba/ttl3/spi/package-info.java @@ -0,0 +1,8 @@ +/** + * TTL SPI + * + * @author Jerry Lee (oldratlee at gmail dot com) + * @see com.alibaba.ttl3.spi.TtlEnhanced + * @see com.alibaba.ttl3.spi.TtlAttachments + */ +package com.alibaba.ttl3.spi; diff --git a/ttl-core/src/main/java/com/alibaba/ttl3/threadpool/ComparableComparator.java b/ttl-core/src/main/java/com/alibaba/ttl3/threadpool/ComparableComparator.java new file mode 100644 index 000000000..8580421a2 --- /dev/null +++ b/ttl-core/src/main/java/com/alibaba/ttl3/threadpool/ComparableComparator.java @@ -0,0 +1,137 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.ttl3.threadpool; + + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// +// This source code file is copied and small adopted from commons-collections4 4.4: +// +// https://github.com/apache/commons-collections/blob/commons-commons-collections-4.4/src/main/java/org/apache/commons/collections4/comparators/ComparableComparator.java +// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +import java.io.Serializable; +import java.util.Comparator; + +/** + * A {@link Comparator Comparator} that compares {@link Comparable Comparable} + * objects. + *

+ * This Comparator is useful, for example, for enforcing the natural order in + * custom implementations of {@link java.util.SortedSet SortedSet} and + * {@link java.util.SortedMap SortedMap}. + *

+ *

+ * Note: In the 2.0 and 2.1 releases of Commons Collections, this class would + * throw a {@link ClassCastException} if either of the arguments to + * {@link #compare compare} were null, not + * {@link Comparable Comparable}, or for which + * {@link Comparable#compareTo(Object) compareTo} gave inconsistent results. + * This is no longer the case. See {@link #compare compare} for + * details. + *

+ * + * @param the type of objects compared by this comparator + * + * @see java.util.Collections#reverseOrder() + */ +class ComparableComparator> implements Comparator, Serializable { + + /** Serialization version. */ + private static final long serialVersionUID=-291439688585137865L; + + /** The singleton instance. */ + @SuppressWarnings("rawtypes") + public static final ComparableComparator INSTANCE = new ComparableComparator(); + + //----------------------------------------------------------------------- + /** + * Gets the singleton instance of a ComparableComparator. + *

+ * Developers are encouraged to use the comparator returned from this method + * instead of constructing a new instance to reduce allocation and GC overhead + * when multiple comparable comparators may be used in the same VM. + * + * @param the element type + * @return the singleton ComparableComparator + */ + @SuppressWarnings("unchecked") + public static > ComparableComparator comparableComparator() { + return INSTANCE; + } + + //----------------------------------------------------------------------- + /** + * Constructor whose use should be avoided. + *

+ * Please use the {@link #comparableComparator()} method whenever possible. + */ + public ComparableComparator() { + super(); + } + + //----------------------------------------------------------------------- + /** + * Compare the two {@link Comparable Comparable} arguments. + * This method is equivalent to: + *

((Comparable)obj1).compareTo(obj2)
+ * + * @param obj1 the first object to compare + * @param obj2 the second object to compare + * @return negative if obj1 is less, positive if greater, zero if equal + * @throws NullPointerException if obj1 is null, + * or when ((Comparable)obj1).compareTo(obj2) does + * @throws ClassCastException if obj1 is not a Comparable, + * or when ((Comparable)obj1).compareTo(obj2) does + */ + @Override + public int compare(final E obj1, final E obj2) { + return obj1.compareTo(obj2); + } + + //----------------------------------------------------------------------- + /** + * Implement a hash code for this comparator that is consistent with + * {@link #equals(Object) equals}. + * + * @return a hash code for this comparator. + */ + @Override + public int hashCode() { + return "ComparableComparator".hashCode(); + } + + /** + * Returns {@code true} iff that Object is is a {@link Comparator Comparator} + * whose ordering is known to be equivalent to mine. + *

+ * This implementation returns {@code true} iff + * object.{@link Object#getClass() getClass()} equals + * this.getClass(). Subclasses may want to override this behavior to remain + * consistent with the {@link Comparator#equals(Object)} contract. + * + * @param object the object to compare with + * @return {@code true} if equal + */ + @Override + public boolean equals(final Object object) { + return this == object || + null != object && object.getClass().equals(this.getClass()); + } + +} diff --git a/ttl-core/src/main/java/com/alibaba/ttl3/threadpool/DisableInheritableForkJoinWorkerThreadFactory.java b/ttl-core/src/main/java/com/alibaba/ttl3/threadpool/DisableInheritableForkJoinWorkerThreadFactory.java new file mode 100644 index 000000000..09d654af5 --- /dev/null +++ b/ttl-core/src/main/java/com/alibaba/ttl3/threadpool/DisableInheritableForkJoinWorkerThreadFactory.java @@ -0,0 +1,20 @@ +package com.alibaba.ttl3.threadpool; + +import com.alibaba.ttl3.spi.TtlWrapper; +import edu.umd.cs.findbugs.annotations.NonNull; + +import java.util.concurrent.ForkJoinPool.ForkJoinWorkerThreadFactory; + +/** + * Disable inheritable {@link ForkJoinWorkerThreadFactory}. + * + * @author Jerry Lee (oldratlee at gmail dot com) + */ +public interface DisableInheritableForkJoinWorkerThreadFactory extends ForkJoinWorkerThreadFactory, TtlWrapper { + /** + * Unwrap {@link DisableInheritableThreadFactory} to the original/underneath one. + */ + @NonNull + @Override + ForkJoinWorkerThreadFactory unwrap(); +} diff --git a/ttl-core/src/main/java/com/alibaba/ttl3/threadpool/DisableInheritableForkJoinWorkerThreadFactoryWrapper.java b/ttl-core/src/main/java/com/alibaba/ttl3/threadpool/DisableInheritableForkJoinWorkerThreadFactoryWrapper.java new file mode 100644 index 000000000..5bc240748 --- /dev/null +++ b/ttl-core/src/main/java/com/alibaba/ttl3/threadpool/DisableInheritableForkJoinWorkerThreadFactoryWrapper.java @@ -0,0 +1,58 @@ +package com.alibaba.ttl3.threadpool; + +import com.alibaba.crr.composite.Backup; +import edu.umd.cs.findbugs.annotations.NonNull; + +import java.util.concurrent.ForkJoinPool; +import java.util.concurrent.ForkJoinPool.ForkJoinWorkerThreadFactory; +import java.util.concurrent.ForkJoinWorkerThread; + +import static com.alibaba.ttl3.transmitter.Transmitter.clear; +import static com.alibaba.ttl3.transmitter.Transmitter.restore; + +/** + * @author Jerry Lee (oldratlee at gmail dot com) + */ +class DisableInheritableForkJoinWorkerThreadFactoryWrapper implements DisableInheritableForkJoinWorkerThreadFactory { + private final ForkJoinWorkerThreadFactory threadFactory; + + DisableInheritableForkJoinWorkerThreadFactoryWrapper(@NonNull ForkJoinWorkerThreadFactory threadFactory) { + this.threadFactory = threadFactory; + } + + @Override + public ForkJoinWorkerThread newThread(ForkJoinPool pool) { + final Backup backup = clear(); + try { + return threadFactory.newThread(pool); + } finally { + restore(backup); + } + } + + @Override + @NonNull + 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; + } +} diff --git a/ttl-core/src/main/java/com/alibaba/ttl3/threadpool/DisableInheritableThreadFactory.java b/ttl-core/src/main/java/com/alibaba/ttl3/threadpool/DisableInheritableThreadFactory.java new file mode 100644 index 000000000..981f23b57 --- /dev/null +++ b/ttl-core/src/main/java/com/alibaba/ttl3/threadpool/DisableInheritableThreadFactory.java @@ -0,0 +1,21 @@ +package com.alibaba.ttl3.threadpool; + +import com.alibaba.ttl3.spi.TtlWrapper; +import edu.umd.cs.findbugs.annotations.NonNull; + +import java.util.concurrent.ThreadFactory; + +/** + * Disable inheritable {@link ThreadFactory}. + * + * @author Jerry Lee (oldratlee at gmail dot com) + * @see ThreadFactory + */ +public interface DisableInheritableThreadFactory extends ThreadFactory, TtlWrapper { + /** + * Unwrap {@link DisableInheritableThreadFactory} to the original/underneath one. + */ + @NonNull + @Override + ThreadFactory unwrap(); +} diff --git a/ttl-core/src/main/java/com/alibaba/ttl3/threadpool/DisableInheritableThreadFactoryWrapper.java b/ttl-core/src/main/java/com/alibaba/ttl3/threadpool/DisableInheritableThreadFactoryWrapper.java new file mode 100644 index 000000000..092ba7378 --- /dev/null +++ b/ttl-core/src/main/java/com/alibaba/ttl3/threadpool/DisableInheritableThreadFactoryWrapper.java @@ -0,0 +1,56 @@ +package com.alibaba.ttl3.threadpool; + +import com.alibaba.crr.composite.Backup; +import edu.umd.cs.findbugs.annotations.NonNull; + +import java.util.concurrent.ThreadFactory; + +import static com.alibaba.ttl3.transmitter.Transmitter.clear; +import static com.alibaba.ttl3.transmitter.Transmitter.restore; + +/** + * @author Jerry Lee (oldratlee at gmail dot com) + */ +class DisableInheritableThreadFactoryWrapper implements DisableInheritableThreadFactory { + private final ThreadFactory threadFactory; + + DisableInheritableThreadFactoryWrapper(@NonNull ThreadFactory threadFactory) { + this.threadFactory = threadFactory; + } + + @Override + public Thread newThread(@NonNull Runnable r) { + final Backup backup = clear(); + try { + return threadFactory.newThread(r); + } finally { + restore(backup); + } + } + + @NonNull + @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; + } +} diff --git a/ttl-core/src/main/java/com/alibaba/ttl3/threadpool/ExecutorServiceTtlWrapper.java b/ttl-core/src/main/java/com/alibaba/ttl3/threadpool/ExecutorServiceTtlWrapper.java new file mode 100644 index 000000000..996b97718 --- /dev/null +++ b/ttl-core/src/main/java/com/alibaba/ttl3/threadpool/ExecutorServiceTtlWrapper.java @@ -0,0 +1,102 @@ +package com.alibaba.ttl3.threadpool; + +import com.alibaba.ttl3.TransmittableThreadLocal; +import com.alibaba.ttl3.TtlCallable; +import com.alibaba.ttl3.TtlRunnable; +import com.alibaba.ttl3.spi.TtlEnhanced; +import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; + +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) + */ +@SuppressFBWarnings({"EQ_DOESNT_OVERRIDE_EQUALS"}) +class ExecutorServiceTtlWrapper extends ExecutorTtlWrapper implements ExecutorService, TtlEnhanced { + private final ExecutorService executorService; + + ExecutorServiceTtlWrapper(@NonNull ExecutorService executorService, boolean idempotent) { + super(executorService, idempotent); + this.executorService = executorService; + } + + @Override + public void shutdown() { + executorService.shutdown(); + } + + @NonNull + @Override + public List shutdownNow() { + return executorService.shutdownNow(); + } + + @Override + public boolean isShutdown() { + return executorService.isShutdown(); + } + + @Override + public boolean isTerminated() { + return executorService.isTerminated(); + } + + @Override + public boolean awaitTermination(long timeout, @NonNull TimeUnit unit) throws InterruptedException { + return executorService.awaitTermination(timeout, unit); + } + + @NonNull + @Override + public Future submit(@NonNull Callable task) { + return executorService.submit(TtlCallable.get(task, false, idempotent)); + } + + @NonNull + @Override + public Future submit(@NonNull Runnable task, T result) { + return executorService.submit(TtlRunnable.get(task, false, idempotent), result); + } + + @NonNull + @Override + public Future submit(@NonNull Runnable task) { + return executorService.submit(TtlRunnable.get(task, false, idempotent)); + } + + @NonNull + @Override + public List> invokeAll(@NonNull Collection> tasks) throws InterruptedException { + return executorService.invokeAll(TtlCallable.gets(tasks, false, idempotent)); + } + + @NonNull + @Override + public List> invokeAll(@NonNull Collection> tasks, long timeout, @NonNull TimeUnit unit) throws InterruptedException { + return executorService.invokeAll(TtlCallable.gets(tasks, false, idempotent), timeout, unit); + } + + @NonNull + @Override + public T invokeAny(@NonNull Collection> tasks) throws InterruptedException, ExecutionException { + return executorService.invokeAny(TtlCallable.gets(tasks, false, idempotent)); + } + + @Override + public T invokeAny(@NonNull Collection> tasks, long timeout, @NonNull TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { + return executorService.invokeAny(TtlCallable.gets(tasks, false, idempotent), timeout, unit); + } + + @NonNull + @Override + public ExecutorService unwrap() { + return executorService; + } +} diff --git a/ttl-core/src/main/java/com/alibaba/ttl3/threadpool/ExecutorTtlWrapper.java b/ttl-core/src/main/java/com/alibaba/ttl3/threadpool/ExecutorTtlWrapper.java new file mode 100644 index 000000000..d9f07e43a --- /dev/null +++ b/ttl-core/src/main/java/com/alibaba/ttl3/threadpool/ExecutorTtlWrapper.java @@ -0,0 +1,57 @@ +package com.alibaba.ttl3.threadpool; + +import com.alibaba.ttl3.TransmittableThreadLocal; +import com.alibaba.ttl3.TtlRunnable; +import com.alibaba.ttl3.spi.TtlEnhanced; +import com.alibaba.ttl3.spi.TtlWrapper; +import edu.umd.cs.findbugs.annotations.NonNull; + +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) + */ +class ExecutorTtlWrapper implements Executor, TtlWrapper, TtlEnhanced { + private final Executor executor; + protected final boolean idempotent; + + ExecutorTtlWrapper(@NonNull Executor executor, boolean idempotent) { + this.executor = executor; + this.idempotent = idempotent; + } + + @Override + public void execute(@NonNull Runnable command) { + executor.execute(TtlRunnable.get(command, false, idempotent)); + } + + @NonNull + @Override + 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; + } +} diff --git a/ttl-core/src/main/java/com/alibaba/ttl3/threadpool/ScheduledExecutorServiceTtlWrapper.java b/ttl-core/src/main/java/com/alibaba/ttl3/threadpool/ScheduledExecutorServiceTtlWrapper.java new file mode 100644 index 000000000..a7c6b0ea6 --- /dev/null +++ b/ttl-core/src/main/java/com/alibaba/ttl3/threadpool/ScheduledExecutorServiceTtlWrapper.java @@ -0,0 +1,60 @@ +package com.alibaba.ttl3.threadpool; + +import com.alibaba.ttl3.TransmittableThreadLocal; +import com.alibaba.ttl3.TtlCallable; +import com.alibaba.ttl3.TtlRunnable; +import com.alibaba.ttl3.spi.TtlEnhanced; +import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; + +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) + */ +@SuppressFBWarnings({"EQ_DOESNT_OVERRIDE_EQUALS"}) +class ScheduledExecutorServiceTtlWrapper extends ExecutorServiceTtlWrapper implements ScheduledExecutorService, TtlEnhanced { + final ScheduledExecutorService scheduledExecutorService; + + public ScheduledExecutorServiceTtlWrapper(@NonNull ScheduledExecutorService scheduledExecutorService, boolean idempotent) { + super(scheduledExecutorService, idempotent); + this.scheduledExecutorService = scheduledExecutorService; + } + + @NonNull + @Override + public ScheduledFuture schedule(@NonNull Runnable command, long delay, @NonNull TimeUnit unit) { + return scheduledExecutorService.schedule(TtlRunnable.get(command, false, idempotent), delay, unit); + } + + @NonNull + @Override + public ScheduledFuture schedule(@NonNull Callable callable, long delay, @NonNull TimeUnit unit) { + return scheduledExecutorService.schedule(TtlCallable.get(callable, false, idempotent), delay, unit); + } + + @NonNull + @Override + public ScheduledFuture scheduleAtFixedRate(@NonNull Runnable command, long initialDelay, long period, @NonNull TimeUnit unit) { + return scheduledExecutorService.scheduleAtFixedRate(TtlRunnable.get(command, false, idempotent), initialDelay, period, unit); + } + + @NonNull + @Override + public ScheduledFuture scheduleWithFixedDelay(@NonNull Runnable command, long initialDelay, long delay, @NonNull TimeUnit unit) { + return scheduledExecutorService.scheduleWithFixedDelay(TtlRunnable.get(command, false, idempotent), initialDelay, delay, unit); + } + + @NonNull + @Override + public ScheduledExecutorService unwrap() { + return scheduledExecutorService; + } +} diff --git a/ttl-core/src/main/java/com/alibaba/ttl3/threadpool/TtlExecutors.java b/ttl-core/src/main/java/com/alibaba/ttl3/threadpool/TtlExecutors.java new file mode 100644 index 000000000..74a15f00e --- /dev/null +++ b/ttl-core/src/main/java/com/alibaba/ttl3/threadpool/TtlExecutors.java @@ -0,0 +1,360 @@ +package com.alibaba.ttl3.threadpool; + +import com.alibaba.ttl3.TransmittableThreadLocal; +import com.alibaba.ttl3.spi.TtlEnhanced; +import com.alibaba.ttl3.spi.TtlWrapper; +import com.alibaba.ttl3.threadpool.agent.TtlAgent; +import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; +import org.jetbrains.annotations.Contract; + +import java.util.Comparator; +import java.util.concurrent.*; + +/** + * Util methods for TTL wrapper of jdk executors. + * + *

    + *
  1. wrap(factory)/check/unwrap methods for TTL wrapper of jdk executors({@link Executor}, {@link ExecutorService}, {@link ScheduledExecutorService}).
  2. + *
  3. Util methods to wrap/unwrap/check methods to disable Inheritable for {@link ForkJoinPool.ForkJoinWorkerThreadFactory}.
  4. + *
  5. wrap/unwrap/check methods to disable Inheritable for {@link ThreadFactory}.
  6. + *
  7. wrap/unwrap/check methods to {@code TtlRunnableUnwrapComparator} for {@link PriorityBlockingQueue}.
  8. + *
+ *

+ * Note: + *

    + *
  • all method is {@code null}-safe, when input {@code executor} parameter is {@code null}, return {@code null}.
  • + *
  • skip wrap/decoration thread pool/{@code executor}(aka. just return input {@code executor}) + * when ttl agent is loaded, Or when input {@code executor} is already wrapped/decorated.
  • + *
+ * + * @author Jerry Lee (oldratlee at gmail dot com) + * @see Executor + * @see ExecutorService + * @see ScheduledExecutorService + * @see ThreadPoolExecutor + * @see ScheduledThreadPoolExecutor + * @see Executors + * @see CompletionService + * @see ExecutorCompletionService + * @see ThreadFactory + * @see Executors#defaultThreadFactory() + * @see PriorityBlockingQueue + * @see ForkJoinPool + * @see ForkJoinPool.ForkJoinWorkerThreadFactory + * @see ForkJoinPool#defaultForkJoinWorkerThreadFactory + * @see java.util.stream.Stream + */ +public final class TtlExecutors { + + /////////////////////////////////////////////////////////////////////////// + // Executor utils + /////////////////////////////////////////////////////////////////////////// + + /** + * {@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}. + *

+ * NOTE: sine v2.12.0 the idempotency of return wrapped Executor is changed to true, + * so the wrapped Executor can be cooperated with the usage of "Decorate Runnable and Callable". + *

+ * About idempotency: if is idempotent, + * it's allowed to submit the {@link com.alibaba.ttl3.TtlRunnable}/{@link com.alibaba.ttl3.TtlCallable} to the wrapped Executor; + * otherwise throw {@link IllegalStateException}. + * + * @param executor input Executor + * @return wrapped Executor + * @see com.alibaba.ttl3.TtlRunnable#get(Runnable, boolean, boolean) + * @see com.alibaba.ttl3.TtlCallable#get(Callable, boolean, boolean) + */ + @Nullable + @Contract(value = "null -> null; !null -> !null", pure = true) + public static Executor getTtlExecutor(@Nullable Executor executor) { + if (TtlAgent.isTtlAgentLoaded() || null == executor || executor instanceof TtlEnhanced) { + return executor; + } + return new ExecutorTtlWrapper(executor, true); + } + + /** + * {@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}. + *

+ * NOTE: sine v2.12.0 the idempotency of return wrapped ExecutorService is changed to true, + * so the wrapped ExecutorService can be cooperated with the usage of "Decorate Runnable and Callable". + *

+ * About idempotency: if is idempotent, + * it's allowed to submit the {@link com.alibaba.ttl3.TtlRunnable}/{@link com.alibaba.ttl3.TtlCallable} to the wrapped ExecutorService; + * otherwise throw {@link IllegalStateException}. + * + * @param executorService input ExecutorService + * @return wrapped ExecutorService + * @see com.alibaba.ttl3.TtlRunnable#get(Runnable, boolean, boolean) + * @see com.alibaba.ttl3.TtlCallable#get(Callable, boolean, boolean) + */ + @Nullable + @Contract(value = "null -> null; !null -> !null", pure = true) + public static ExecutorService getTtlExecutorService(@Nullable ExecutorService executorService) { + if (TtlAgent.isTtlAgentLoaded() || executorService == null || executorService instanceof TtlEnhanced) { + return executorService; + } + return new ExecutorServiceTtlWrapper(executorService, true); + } + + + /** + * {@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}. + *

+ * NOTE: sine v2.12.0 the idempotency of return wrapped ScheduledExecutorService is changed to true, + * so the wrapped ScheduledExecutorService can be cooperated with the usage of "Decorate Runnable and Callable". + *

+ * About idempotency: if is idempotent, + * it's allowed to submit the {@link com.alibaba.ttl3.TtlRunnable}/{@link com.alibaba.ttl3.TtlCallable} to the wrapped ScheduledExecutorService; + * otherwise throw {@link IllegalStateException}. + * + * @param scheduledExecutorService input scheduledExecutorService + * @return wrapped scheduledExecutorService + * @see com.alibaba.ttl3.TtlRunnable#get(Runnable, boolean, boolean) + * @see com.alibaba.ttl3.TtlCallable#get(Callable, boolean, boolean) + */ + @Nullable + @Contract(value = "null -> null; !null -> !null", pure = true) + public static ScheduledExecutorService getTtlScheduledExecutorService(@Nullable ScheduledExecutorService scheduledExecutorService) { + if (TtlAgent.isTtlAgentLoaded() || scheduledExecutorService == null || scheduledExecutorService instanceof TtlEnhanced) { + return scheduledExecutorService; + } + return new ScheduledExecutorServiceTtlWrapper(scheduledExecutorService, true); + } + + /** + * check the executor is a TTL executor wrapper or not. + *

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

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

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

+ * NOTE: if input executor is {@code null}, return {@code null}. + * + * @param executor input executor + * @param Executor type + * @see #getTtlExecutor(Executor) + * @see #getTtlExecutorService(ExecutorService) + * @see #getTtlScheduledExecutorService(ScheduledExecutorService) + * @see #isTtlExecutorWrapper(Executor) + * @see com.alibaba.ttl3.TtlWrappers#unwrap(Object) + */ + @Nullable + @Contract(value = "null -> null; !null -> !null", pure = true) + @SuppressWarnings("unchecked") + public static T unwrapExecutor(@Nullable T executor) { + if (!isTtlExecutorWrapper(executor)) return executor; + + return (T) ((ExecutorTtlWrapper) executor).unwrap(); + } + + /** + * Wrapper of {@link ThreadFactory}, disable inheritable. + * + * @param threadFactory input thread factory + * @see DisableInheritableThreadFactory + * @see TtlExecutors#getDisableInheritableForkJoinWorkerThreadFactory + */ + @Nullable + @Contract(value = "null -> null; !null -> !null", pure = true) + 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) + * @see TtlExecutors#getDefaultDisableInheritableForkJoinWorkerThreadFactory() + */ + @NonNull + public static ThreadFactory getDefaultDisableInheritableThreadFactory() { + return getDisableInheritableThreadFactory(Executors.defaultThreadFactory()); + } + + /** + * check the {@link ThreadFactory} is {@link DisableInheritableThreadFactory} or not. + * + * @see TtlExecutors#getDisableInheritableForkJoinWorkerThreadFactory + * @see #getDefaultDisableInheritableThreadFactory() + * @see DisableInheritableThreadFactory + */ + public static boolean isDisableInheritableThreadFactory(@Nullable ThreadFactory threadFactory) { + return threadFactory instanceof DisableInheritableThreadFactory; + } + + /** + * Unwrap {@link DisableInheritableThreadFactory} to the original/underneath one. + * + * @see #getDisableInheritableThreadFactory(ThreadFactory) + * @see #getDefaultDisableInheritableThreadFactory() + * @see #isDisableInheritableThreadFactory(ThreadFactory) + * @see TtlExecutors#unwrapForkJoinWorkerThreadFactory(ForkJoinPool.ForkJoinWorkerThreadFactory) + * @see DisableInheritableThreadFactory + * @see com.alibaba.ttl3.TtlWrappers#unwrap(Object) + */ + @Nullable + @Contract(value = "null -> null; !null -> !null", pure = true) + public static ThreadFactory unwrapThreadFactory(@Nullable ThreadFactory threadFactory) { + if (!isDisableInheritableThreadFactory(threadFactory)) return threadFactory; + + return ((DisableInheritableThreadFactory) threadFactory).unwrap(); + } + + /////////////////////////////////////////////////////////////////////////// + // ForkJoinPool utils + /////////////////////////////////////////////////////////////////////////// + + /** + * Wrapper of {@link ForkJoinPool.ForkJoinWorkerThreadFactory}, disable inheritable. + * + * @param threadFactory input thread factory + * @see DisableInheritableForkJoinWorkerThreadFactory + * @see TtlExecutors#getDisableInheritableThreadFactory(ThreadFactory) + */ + @Nullable + @Contract(value = "null -> null; !null -> !null", pure = true) + public static ForkJoinPool.ForkJoinWorkerThreadFactory getDisableInheritableForkJoinWorkerThreadFactory(@Nullable ForkJoinPool.ForkJoinWorkerThreadFactory threadFactory) { + if (threadFactory == null || isDisableInheritableForkJoinWorkerThreadFactory(threadFactory)) + return threadFactory; + + return new DisableInheritableForkJoinWorkerThreadFactoryWrapper(threadFactory); + } + + /** + * Wrapper of {@link ForkJoinPool#defaultForkJoinWorkerThreadFactory}, disable inheritable. + * + * @see #getDisableInheritableForkJoinWorkerThreadFactory(ForkJoinPool.ForkJoinWorkerThreadFactory) + * @see TtlExecutors#getDefaultDisableInheritableThreadFactory() + */ + @NonNull + public static ForkJoinPool.ForkJoinWorkerThreadFactory getDefaultDisableInheritableForkJoinWorkerThreadFactory() { + return getDisableInheritableForkJoinWorkerThreadFactory(ForkJoinPool.defaultForkJoinWorkerThreadFactory); + } + + /** + * check the {@link ForkJoinPool.ForkJoinWorkerThreadFactory} is {@link DisableInheritableForkJoinWorkerThreadFactory} or not. + * + * @see #getDisableInheritableForkJoinWorkerThreadFactory(ForkJoinPool.ForkJoinWorkerThreadFactory) + * @see #getDefaultDisableInheritableForkJoinWorkerThreadFactory() + * @see DisableInheritableForkJoinWorkerThreadFactory + */ + public static boolean isDisableInheritableForkJoinWorkerThreadFactory(@Nullable ForkJoinPool.ForkJoinWorkerThreadFactory threadFactory) { + return threadFactory instanceof DisableInheritableForkJoinWorkerThreadFactory; + } + + /** + * Unwrap {@link DisableInheritableForkJoinWorkerThreadFactory} to the original/underneath one. + * + * @see com.alibaba.ttl3.TtlWrappers#unwrap(Object) + * @see DisableInheritableForkJoinWorkerThreadFactory + * @see TtlExecutors#unwrapThreadFactory(ThreadFactory) + */ + @Nullable + @Contract(value = "null -> null; !null -> !null", pure = true) + public static ForkJoinPool.ForkJoinWorkerThreadFactory unwrapForkJoinWorkerThreadFactory(@Nullable ForkJoinPool.ForkJoinWorkerThreadFactory threadFactory) { + if (!isDisableInheritableForkJoinWorkerThreadFactory(threadFactory)) return threadFactory; + + return ((DisableInheritableForkJoinWorkerThreadFactory) threadFactory).unwrap(); + } + + /////////////////////////////////////////////////////////////////////////// + // Comparator utils + /////////////////////////////////////////////////////////////////////////// + + /** + * Wrapper of {@code Comparator} which unwrap {@link com.alibaba.ttl3.TtlRunnable} before compare, + * aka {@code TtlRunnableUnwrapComparator}. + *

+ * Prepared for {@code comparator} parameter of constructor + * {@link PriorityBlockingQueue#PriorityBlockingQueue(int, Comparator)}. + *

+ * {@link PriorityBlockingQueue} can be used by constructor + * {@link ThreadPoolExecutor#ThreadPoolExecutor(int, int, long, TimeUnit, BlockingQueue)}. + * + * @param comparator input comparator + * @return wrapped comparator + * @see ThreadPoolExecutor + * @see ThreadPoolExecutor#ThreadPoolExecutor(int, int, long, TimeUnit, BlockingQueue) + * @see PriorityBlockingQueue + * @see PriorityBlockingQueue#PriorityBlockingQueue(int, Comparator) + */ + @Nullable + @Contract(value = "null -> null; !null -> !null", pure = true) + public static Comparator getTtlRunnableUnwrapComparator(@Nullable Comparator comparator) { + if (comparator == null || isTtlRunnableUnwrapComparator(comparator)) return comparator; + + return new TtlUnwrapComparator<>(comparator); + } + + @SuppressWarnings({"unchecked", "rawtypes"}) + private static final Comparator INSTANCE = new TtlUnwrapComparator(ComparableComparator.INSTANCE); + + /** + * {@code TtlRunnableUnwrapComparator} that compares {@link Comparable Comparable} objects. + * + * @see #getTtlRunnableUnwrapComparator(Comparator) + */ + @NonNull + @SuppressWarnings("unchecked") + public static Comparator getTtlRunnableUnwrapComparatorForComparableRunnable() { + return (Comparator) INSTANCE; + } + + /** + * check the {@code Comparator} is a wrapper {@code TtlRunnableUnwrapComparator} or not. + * + * @see #getTtlRunnableUnwrapComparator(Comparator) + * @see #getTtlRunnableUnwrapComparatorForComparableRunnable() + */ + public static boolean isTtlRunnableUnwrapComparator(@Nullable Comparator comparator) { + return comparator instanceof TtlUnwrapComparator; + } + + /** + * Unwrap {@code TtlRunnableUnwrapComparator} to the original/underneath {@code Comparator}. + * + * @see #getTtlRunnableUnwrapComparator(Comparator) + * @see #getTtlRunnableUnwrapComparatorForComparableRunnable() + * @see #isTtlRunnableUnwrapComparator(Comparator) + * @see com.alibaba.ttl3.TtlWrappers#unwrap(Object) + */ + @Nullable + @Contract(value = "null -> null; !null -> !null", pure = true) + public static Comparator unwrapComparator(@Nullable Comparator comparator) { + if (!isTtlRunnableUnwrapComparator(comparator)) return comparator; + + return ((TtlUnwrapComparator) comparator).unwrap(); + } + + private TtlExecutors() { + throw new InstantiationError("Must not instantiate this class"); + } +} diff --git a/ttl-core/src/main/java/com/alibaba/ttl3/threadpool/TtlUnwrapComparator.java b/ttl-core/src/main/java/com/alibaba/ttl3/threadpool/TtlUnwrapComparator.java new file mode 100644 index 000000000..1d671f7c4 --- /dev/null +++ b/ttl-core/src/main/java/com/alibaba/ttl3/threadpool/TtlUnwrapComparator.java @@ -0,0 +1,51 @@ +package com.alibaba.ttl3.threadpool; + +import com.alibaba.ttl3.TtlWrappers; +import com.alibaba.ttl3.spi.TtlWrapper; +import edu.umd.cs.findbugs.annotations.NonNull; + +import java.util.Comparator; + +/** + * @see TtlExecutors#getTtlRunnableUnwrapComparator(Comparator) + * @see TtlExecutors#isTtlRunnableUnwrapComparator(Comparator) + * @see TtlExecutors#unwrapComparator(Comparator) + */ +final class TtlUnwrapComparator implements Comparator, TtlWrapper> { + private final Comparator comparator; + + public TtlUnwrapComparator(@NonNull Comparator comparator) { + this.comparator = comparator; + } + + @Override + public int compare(T o1, T o2) { + return comparator.compare(TtlWrappers.unwrap(o1), TtlWrappers.unwrap(o2)); + } + + @NonNull + @Override + public Comparator unwrap() { + return comparator; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + TtlUnwrapComparator that = (TtlUnwrapComparator) o; + + return comparator.equals(that.comparator); + } + + @Override + public int hashCode() { + return comparator.hashCode(); + } + + @Override + public String toString() { + return "TtlUnwrapComparator{comparator=" + comparator + '}'; + } +} diff --git a/ttl-core/src/main/java/com/alibaba/ttl3/threadpool/agent/TtlAgent.java b/ttl-core/src/main/java/com/alibaba/ttl3/threadpool/agent/TtlAgent.java new file mode 100644 index 000000000..d4446e9b2 --- /dev/null +++ b/ttl-core/src/main/java/com/alibaba/ttl3/threadpool/agent/TtlAgent.java @@ -0,0 +1,372 @@ +package com.alibaba.ttl3.threadpool.agent; + +import com.alibaba.ttl3.threadpool.agent.logging.Logger; +import com.alibaba.ttl3.threadpool.agent.transformlet.TtlTransformlet; +import com.alibaba.ttl3.threadpool.agent.transformlet.internal.ForkJoinTtlTransformlet; +import com.alibaba.ttl3.threadpool.agent.transformlet.internal.JdkExecutorTtlTransformlet; +import com.alibaba.ttl3.threadpool.agent.transformlet.internal.PriorityBlockingQueueTtlTransformlet; +import com.alibaba.ttl3.threadpool.agent.transformlet.internal.TimerTaskTtlTransformlet; +import com.alibaba.ttl3.threadpool.DisableInheritableForkJoinWorkerThreadFactory; +import com.alibaba.ttl3.threadpool.DisableInheritableThreadFactory; +import com.alibaba.ttl3.threadpool.TtlExecutors; +import edu.umd.cs.findbugs.annotations.NonNull; + +import java.lang.instrument.ClassFileTransformer; +import java.lang.instrument.Instrumentation; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * TTL Java Agent. + * + *

The configuration for TTL agent

+ *

+ * Configure TTL agent via {@code -D property}({@link System#getProperties()}) or TTL agent arguments. + *

    + *
  1. {@code -D property} config format is: {@code -Dkey1=v2 -Dkey2=v2}
  2. + *
  3. TTL agent arguments config format is {@code key1:v1,key2:v2}.
    + * separate key-value pairs by {@code char ,}, and separate key-value by {@code char :}.
  4. + *
+ * NOTE about the config sources and the precedence:
+ *
    + *
  1. Read {@code -D property}({@link System#getProperties()}) first.
  2. + *
  3. if no {@code -D property} configured(including empty property value configured by {@code -Dkey1}/{@code -Dkey1=}), read TTL Agent argument configuration.
  4. + *
+ * Below is available TTL agent configuration keys. + * + *

Configuration key: Log Type

+ *

+ * The log type of TTL Java Agent is configured by key {@code ttl.agent.logger}. Since version {@code 2.6.0}. + * + *

    + *
  • {@code ttl.agent.logger : STDERR}
    + * only log to {@code stderr} when error. + * This is default, when no/unrecognized configuration for key {@code ttl.agent.logger}.
  • + *
  • {@code ttl.agent.logger : STDOUT}
    + * Log to {@code stdout}, more info than {@code ttl.agent.logger:STDERR}; This is needed when developing.
  • + *
+ *

+ * Configuration example: + * + *

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

Configuration key: Disable inheritable for thread pool

+ *

+ * Enable "disable inheritable" for thread pool, configured by key {@code ttl.agent.disable.inheritable.for.thread.pool}. + * When no configuration for this key, default is {@code false}(aka. do NOT disable inheritable). Since version {@code 2.10.1}. + * + *

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

+ * Configuration example: + * + *

    + *
  1. {@code -Dttl.agent.disable.inheritable.for.thread.pool=true}
  2. + *
  3. {@code -javaagent:/path/to/transmittable-thread-local-2.x.y.jar=ttl.agent.disable.inheritable.for.thread.pool:true}
  4. + *
+ * + *

Configuration key: Enable TimerTask class decoration

+ *

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

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

+ * Configuration example: + * + *

    + *
  1. {@code -Dttl.agent.enable.timer.task=false}
  2. + *
  3. {@code -javaagent:/path/to/transmittable-thread-local-2.x.y.jar=ttl.agent.enable.timer.task:false}
  4. + *
+ * + *

Configuration key: logging the transform class received by TTL Agent

+ *

+ * Enable logging the transform class received by TTL Agent by key {@code ttl.agent.log.class.transform}, + * default is {@code false}(aka. do NOT logging the transform class received by TTL Agent). + * Since version {@code 3.0.0}. + *

+ * Configuration example: + * + *

    + *
  1. {@code -Dttl.agent.log.class.transform=true}
  2. + *
  3. {@code -javaagent:/path/to/transmittable-thread-local-2.x.y.jar=ttl.agent.log.class.transform:true}
  4. + *
+ * + *

Multi key configuration example

+ *

+ * For {@code -D property} config, simply specify multiply {@code -D property}, example:
+ * {@code -Dttl.agent.logger=STDOUT -Dttl.agent.disable.inheritable.for.thread.pool=true} + *

+ * For TTL agent arguments config, example:
+ * {@code -javaagent:/path/to/transmittable-thread-local-2.x.y.jar=ttl.agent.logger:STDOUT,ttl.agent.disable.inheritable.for.thread.pool:true} + * + *

About boot classpath for TTL agent

+ *

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

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

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

+ * More info about {@code Boot-Class-Path} see + * The mechanism for instrumentation. + * + * @author Jerry Lee (oldratlee at gmail dot com) + * @see Instrumentation + * @see The mechanism for instrumentation + * @see JAR File Specification - JAR Manifest + * @see Working with Manifest Files - The Java™ Tutorials + * @see com.alibaba.ttl3.TransmittableThreadLocal + * @see java.util.concurrent.ThreadPoolExecutor + * @see java.util.concurrent.ScheduledThreadPoolExecutor + * @see java.util.concurrent.ForkJoinPool + * @see java.util.TimerTask + */ +public final class TtlAgent { + + /** + * the TTL agent configuration key: Log Type + * + * @see TtlAgent + */ + public static final String TTL_AGENT_LOGGER_KEY = "ttl.agent.logger"; + + /** + * the TTL agent configuration key: Disable inheritable for thread pool + * + * @see TtlAgent + */ + public static final String TTL_AGENT_DISABLE_INHERITABLE_FOR_THREAD_POOL_KEY = "ttl.agent.disable.inheritable.for.thread.pool"; + + /** + * the TTL agent configuration key: Enable TimerTask class decoration + * + * @see TtlAgent + */ + public static final String TTL_AGENT_ENABLE_TIMER_TASK_KEY = "ttl.agent.enable.timer.task"; + + /** + * the TTL agent configuration key: logging the transform class received by TTL Agent + * + * @see TtlAgent + */ + public static final String TTL_AGENT_LOG_CLASS_TRANSFORM_KEY = "ttl.agent.log.class.transform"; + + + // ======== TTL Agent internal States ======== + + private static volatile Map kvs; + + private static volatile boolean ttlAgentLoaded = false; + + /** + * Entrance method of TTL Java Agent. + * + * @see TtlAgent + */ + public static void premain(final String agentArgs, @NonNull final Instrumentation inst) { + kvs = TtlAgentHelper.splitCommaColonStringToKV(agentArgs); + + Logger.setLoggerImplType(getLoggerType()); + final Logger logger = Logger.getLogger(TtlAgent.class); + + try { + logger.info("[TtlAgent.premain] begin, agentArgs: " + agentArgs + ", Instrumentation: " + inst); + + logger.info(logTtlAgentConfig()); + + final List transformletList = new ArrayList<>(); + + transformletList.add(new JdkExecutorTtlTransformlet()); + transformletList.add(new PriorityBlockingQueueTtlTransformlet()); + + transformletList.add(new ForkJoinTtlTransformlet()); + + if (isEnableTimerTask()) transformletList.add(new TimerTaskTtlTransformlet()); + + final ClassFileTransformer transformer = new TtlTransformer(transformletList, isLogClassTransform()); + inst.addTransformer(transformer, true); + logger.info("[TtlAgent.premain] add Transformer " + transformer.getClass().getName() + " success"); + + logger.info("[TtlAgent.premain] end"); + + ttlAgentLoaded = true; + } catch (Exception e) { + String msg = "Fail to load TtlAgent , cause: " + e.toString(); + logger.error(msg, e); + throw new IllegalStateException(msg, e); + } + } + + private static String logTtlAgentConfig() { + return "TTL Agent configurations:" + + "\n " + TTL_AGENT_LOGGER_KEY + "=" + getLoggerType() + + "\n " + TTL_AGENT_LOG_CLASS_TRANSFORM_KEY + "=" + isLogClassTransform() + + "\n " + TTL_AGENT_DISABLE_INHERITABLE_FOR_THREAD_POOL_KEY + "=" + isDisableInheritableForThreadPool() + + "\n " + TTL_AGENT_ENABLE_TIMER_TASK_KEY + "=" + isEnableTimerTask(); + } + + /** + * Whether TTL agent is loaded. + * + */ + public static boolean isTtlAgentLoaded() { + return ttlAgentLoaded; + } + + /** + * Whether disable inheritable for thread pool is enhanced by ttl agent, check {@link #isTtlAgentLoaded()} first. + *

+ * Same as {@code isBooleanOptionSet(TTL_AGENT_DISABLE_INHERITABLE_FOR_THREAD_POOL_KEY)}. + * + * @see TtlExecutors#getDefaultDisableInheritableThreadFactory() + * @see TtlExecutors#getDisableInheritableThreadFactory(java.util.concurrent.ThreadFactory) + * @see DisableInheritableThreadFactory + * @see TtlExecutors#getDefaultDisableInheritableForkJoinWorkerThreadFactory() + * @see TtlExecutors#getDisableInheritableForkJoinWorkerThreadFactory(java.util.concurrent.ForkJoinPool.ForkJoinWorkerThreadFactory) + * @see DisableInheritableForkJoinWorkerThreadFactory + * @see com.alibaba.ttl3.TransmittableThreadLocal + * @see TtlAgent + * @see #isBooleanOptionSet(String) + * @see #TTL_AGENT_DISABLE_INHERITABLE_FOR_THREAD_POOL_KEY + */ + public static boolean isDisableInheritableForThreadPool() { + return isBooleanOptionSet(TTL_AGENT_DISABLE_INHERITABLE_FOR_THREAD_POOL_KEY); + } + + /** + * Whether timer task is enhanced by ttl agent, check {@link #isTtlAgentLoaded()} first. + *

+ * Same as {@code isBooleanOptionSet(TTL_AGENT_ENABLE_TIMER_TASK_KEY, true)}. + * + * @see java.util.Timer + * @see java.util.TimerTask + * @see TtlAgent + * @see #isBooleanOptionSet(String, boolean) + * @see #TTL_AGENT_ENABLE_TIMER_TASK_KEY + */ + public static boolean isEnableTimerTask() { + return isBooleanOptionSet(TTL_AGENT_ENABLE_TIMER_TASK_KEY, true); + } + + /** + * Whether logging the transform class received by {@link TtlAgent}. + *

+ * Same as {@code isBooleanOptionSet(TTL_AGENT_LOG_CLASS_TRANSFORM_KEY)}. + * + * @see TtlAgent + * @see #isBooleanOptionSet(String) + * @see #TTL_AGENT_LOG_CLASS_TRANSFORM_KEY + */ + public static boolean isLogClassTransform() { + return isBooleanOptionSet(TTL_AGENT_LOG_CLASS_TRANSFORM_KEY); + } + + /** + * Get the TTL Agent Log type. + *

+ * Same as {@code getStringOptionValue(TTL_AGENT_LOGGER_KEY, Logger.STDERR)}. + * + * @see Logger + * @see Logger#STDERR + * @see Logger#STDOUT + * @see TtlAgent + * @see #getStringOptionValue(String, String) + * @see #TTL_AGENT_LOGGER_KEY + */ + @NonNull + public static String getLoggerType() { + return getStringOptionValue(TTL_AGENT_LOGGER_KEY, Logger.STDERR); + } + + // ======== Generic Option Getters ======== + + /** + * Generic Option Getters for {@code boolean type} option. + *

+ * Same as {@code isBooleanOptionSet(key, false)}. + * + * @see #isBooleanOptionSet(String, boolean) + * @see TtlAgent + */ + public static boolean isBooleanOptionSet(@NonNull String key) { + return isBooleanOptionSet(key, false); + } + + /** + * Generic Option Getters for {@code boolean type} option. + * + * @see TtlAgent + */ + public static boolean isBooleanOptionSet(@NonNull String key, boolean defaultValueIfKeyAbsent) { + return TtlAgentHelper.isBooleanOptionSet(kvs, key, defaultValueIfKeyAbsent); + } + + /** + * Generic Option Getters for {@code string type} option. + *

+ * usage example: + *

+ * if {@code -Dttl.agent.logger=STDOUT} or + * TTL Agent configuration is {@code -javaagent:/path/to/transmittable-thread-local-2.x.y.jar=ttl.agent.logger:STDOUT}, + * {@code getOptionValue("ttl.agent.logger")} return {@code STDOUT}. + * + * @see TtlAgent + */ + @NonNull + public static String getStringOptionValue(@NonNull String key, @NonNull String defaultValue) { + return TtlAgentHelper.getStringOptionValue(kvs, key, defaultValue); + } + + /** + * Generic Option Getters for {@code string list type} option. + *

+ * TTL configuration use {@code |} to separate items. + *

+ * usage example:
+ * if {@code -Dfoo.list=v1|v2|v3} or + * TTL Agent configuration is {@code -javaagent:/path/to/transmittable-thread-local-2.x.y.jar=foo.list:v1|v2|v3}, + * {@code getOptionValue("foo.list")} return {@code [v1, v2, v3]}. + * + * @see TtlAgent + */ + @NonNull + static List getOptionStringListValues(@NonNull String key) { + return TtlAgentHelper.getOptionStringListValues(kvs, key); + } + + + private TtlAgent() { + throw new InstantiationError("Must not instantiate this class"); + } +} diff --git a/ttl-core/src/main/java/com/alibaba/ttl3/threadpool/agent/TtlAgentHelper.java b/ttl-core/src/main/java/com/alibaba/ttl3/threadpool/agent/TtlAgentHelper.java new file mode 100644 index 000000000..0d4cef68e --- /dev/null +++ b/ttl-core/src/main/java/com/alibaba/ttl3/threadpool/agent/TtlAgentHelper.java @@ -0,0 +1,132 @@ +package com.alibaba.ttl3.threadpool.agent; + +import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; + +import java.util.*; + +/** + * @author Jerry Lee (oldratlee at gmail dot com) + */ +final class TtlAgentHelper { + + // ======== Option Getter Methods ======== + + static boolean isBooleanOptionSet( + @Nullable final Map kvs, @NonNull String key, + boolean defaultValueIfKeyAbsent + ) { + return isBooleanOptionSet(kvs, key, defaultValueIfKeyAbsent, true); + } + + static boolean isBooleanOptionSet( + @Nullable final Map kvs, @NonNull String key, + boolean defaultValueIfKeyAbsent, boolean defaultValueIfValueAbsent + ) { + final String value; + + final Properties properties = System.getProperties(); + if (properties.containsKey(key)) { + value = properties.getProperty(key).trim(); + } else { + if (kvs == null) return defaultValueIfKeyAbsent; + + final boolean containsKey = kvs.containsKey(key); + if (!containsKey) return defaultValueIfKeyAbsent; + + value = kvs.get(key).trim(); + } + + // if value is blank + if (value.isEmpty()) return defaultValueIfValueAbsent; + + return !"false".equalsIgnoreCase(value); + } + + @NonNull + static String getStringOptionValue( + @Nullable final Map kvs, @NonNull String key, + @NonNull String defaultValue + ) { + final String value; + + final Properties properties = System.getProperties(); + if (properties.containsKey(key)) { + value = properties.getProperty(key).trim(); + } else { + if (kvs == null) return defaultValue; + + final boolean containsKey = kvs.containsKey(key); + if (!containsKey) return defaultValue; + + value = kvs.get(key).trim(); + } + + // if value is blank + if (value.isEmpty()) return defaultValue; + + return value; + } + + @NonNull + @SuppressWarnings("unchecked") + static List getOptionStringListValues(@Nullable final Map kvs, @NonNull String key) { + final String value; + + final Properties properties = System.getProperties(); + if (properties.containsKey(key)) { + value = properties.getProperty(key); + } else { + if (kvs == null) return Collections.EMPTY_LIST; + + value = kvs.get(key); + } + + return splitListStringToStringList(value); + } + + // ======== Simple Parse Util Methods ======== + + /** + * Split {@code json} like String({@code "k1:v1,k2:v2"}) to KV map({@code "k1"->"v1", "k2"->"v2"}). + */ + @NonNull + static Map splitCommaColonStringToKV(@Nullable final String commaColonString) { + final Map ret = new HashMap<>(); + if (commaColonString == null || commaColonString.trim().length() == 0) return ret; + + final String[] splitKvArray = commaColonString.trim().split("\\s*,\\s*"); + for (String kvString : splitKvArray) { + final String[] kv = kvString.trim().split("\\s*:\\s*"); + if (kv.length == 0) continue; + + if (kv.length == 1) ret.put(kv[0], ""); + else ret.put(kv[0], kv[1]); + } + + return ret; + } + + /** + * Split String {@code "v1|v2|v3"} to String List({@code [v1, v2, v3]}). + */ + @NonNull + static List splitListStringToStringList(@Nullable String listString) { + final List ret = new ArrayList<>(); + if (listString == null || listString.trim().length() == 0) return ret; + + final String[] split = listString.trim().split("\\s*\\|\\s*"); + for (String s : split) { + if (s.length() == 0) continue; + + ret.add(s); + } + + return ret; + } + + + private TtlAgentHelper() { + throw new InstantiationError("Must not instantiate this class"); + } +} diff --git a/ttl-core/src/main/java/com/alibaba/ttl3/threadpool/agent/TtlExtensionTransformletManager.java b/ttl-core/src/main/java/com/alibaba/ttl3/threadpool/agent/TtlExtensionTransformletManager.java new file mode 100644 index 000000000..c1ae69283 --- /dev/null +++ b/ttl-core/src/main/java/com/alibaba/ttl3/threadpool/agent/TtlExtensionTransformletManager.java @@ -0,0 +1,306 @@ +package com.alibaba.ttl3.threadpool.agent; + +import com.alibaba.ttl3.threadpool.agent.logging.Logger; +import com.alibaba.ttl3.threadpool.agent.transformlet.ClassInfo; +import com.alibaba.ttl3.threadpool.agent.transformlet.TtlTransformlet; +import edu.umd.cs.findbugs.annotations.NonNull; +import javassist.CannotCompileException; +import javassist.NotFoundException; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.lang.reflect.InvocationTargetException; +import java.net.URL; +import java.util.*; + +import static com.alibaba.ttl3.threadpool.agent.transformlet.helper.TtlTransformletHelper.getLocationUrlOfClass; +import static java.nio.charset.StandardCharsets.UTF_8; + +/** + * @author Jerry Lee (oldratlee at gmail dot com) + */ +final class TtlExtensionTransformletManager { + private static final Logger logger = Logger.getLogger(TtlExtensionTransformletManager.class); + + private static final String TTL_AGENT_EXTENSION_TRANSFORMLET_FILE = "META-INF/ttl.agent.transformlets"; + + public TtlExtensionTransformletManager() { + } + + public String extensionTransformletDoTransform(@NonNull final ClassInfo classInfo) throws NotFoundException, CannotCompileException, IOException { + final Map transformlets = classLoader2ExtensionTransformletsIncludeParentCL.get(classInfo.getClassLoader()); + if (transformlets == null) return null; + + for (Map.Entry entry : transformlets.entrySet()) { + final String className = entry.getKey(); + final TtlTransformlet transformlet = entry.getValue(); + + transformlet.doTransform(classInfo); + if (classInfo.isModified()) { + return className; + } + } + + return null; + } + + // NOTE: use WeakHashMap as a Set collection, value is always null. + private final WeakHashMap collectedClassLoaderHistory = new WeakHashMap<>(512); + + // Map: ExtensionTransformlet ClassLoader -> ExtensionTransformlet ClassName -> ExtensionTransformlet instance(not include from parent classloader) + private final WeakHashMap> classLoader2ExtensionTransformlets = + new WeakHashMap<>(512); + + // Map: ExtensionTransformlet ClassLoader -> ExtensionTransformlet ClassName -> ExtensionTransformlet instance(include from parent classloader) + private final WeakHashMap> classLoader2ExtensionTransformletsIncludeParentCL = + new WeakHashMap<>(512); + + public void collectExtensionTransformlet(@NonNull final ClassInfo classInfo) throws IOException { + final ClassLoader classLoader = classInfo.getClassLoader(); + // classloader may null be if the bootstrap loader, + // which classloader must contains NO Ttl Agent Extension Transformlet, so just safe skip + if (classLoader == null) return; + + // this classLoader is collected, so skip collection + if (collectedClassLoaderHistory.containsKey(classLoader)) return; + collectedClassLoaderHistory.put(classLoader, null); + + logger.info("[TtlExtensionTransformletCollector] collecting TTL Extension Transformlets from classloader " + classLoader); + + final LinkedHashSet extensionTransformletClassNames = readExtensionTransformletClassNames(classLoader); + + final String foundMsgHead = "[TtlExtensionTransformletCollector] found TTL Extension Transformlet class "; + final String failLoadMsgHead = "[TtlExtensionTransformletCollector] fail to load TTL Extension Transformlet "; + final Map> loadedTransformlet = + loadExtensionInstances(classLoader, extensionTransformletClassNames, TtlTransformlet.class, foundMsgHead, failLoadMsgHead); + + mergeToClassLoader2ExtensionTransformlet(classLoader2ExtensionTransformlets, loadedTransformlet); + + updateClassLoader2ExtensionTransformletsIncludeParentCL( + classLoader2ExtensionTransformlets, classLoader2ExtensionTransformletsIncludeParentCL); + } + + // extension transformlet configuration file URL location string -> URL contained extension transformlet class names + private final Map> redExtensionTransformletFileHistory = new HashMap<>(); + + private LinkedHashSet readExtensionTransformletClassNames(ClassLoader classLoader) throws IOException { + final Enumeration extensionFiles = classLoader.getResources(TTL_AGENT_EXTENSION_TRANSFORMLET_FILE); + + final Pair, Set> pair = readLinesFromExtensionFiles(extensionFiles, redExtensionTransformletFileHistory); + final LinkedHashSet extensionTransformletClassNames = pair.first; + final Set stringUrls = pair.second; + if (!stringUrls.isEmpty()) + logger.info("[TtlExtensionTransformletCollector] found TTL Extension Transformlet configuration files from classloader " + + classLoader + " : " + stringUrls); + + return extensionTransformletClassNames; + } + + private static void mergeToClassLoader2ExtensionTransformlet( + Map> destination, Map> loadedTransformlets + ) { + for (Map.Entry> entry : loadedTransformlets.entrySet()) { + final ClassLoader classLoader = entry.getKey(); + final Set transformlets = entry.getValue(); + + Map className2Transformlets = destination.computeIfAbsent(classLoader, k -> new HashMap<>()); + + for (TtlTransformlet t : transformlets) { + final String className = t.getClass().getName(); + if (className2Transformlets.containsKey(className)) continue; + + className2Transformlets.put(className, t); + logger.info("[TtlExtensionTransformletCollector] add TTL Extension Transformlet " + className + " success"); + } + } + } + + static void updateClassLoader2ExtensionTransformletsIncludeParentCL( + Map> classLoader2ExtensionTransformlets, + Map> classLoader2ExtensionTransformletsIncludeParentCL + ) { + for (Map.Entry> entry : classLoader2ExtensionTransformlets.entrySet()) { + final ClassLoader classLoader = entry.getKey(); + final Map merged = childClassLoaderFirstMergeTransformlets(classLoader2ExtensionTransformlets, classLoader); + classLoader2ExtensionTransformletsIncludeParentCL.put(classLoader, merged); + } + } + + static Map childClassLoaderFirstMergeTransformlets( + Map> classLoader2Transformlet, ClassLoader classLoader + ) { + Map ret = new HashMap<>(); + + final ArrayDeque chain = new ArrayDeque<>(); + chain.add(classLoader); + while (classLoader.getParent() != null) { + classLoader = classLoader.getParent(); + + chain.addFirst(classLoader); + } + + for (ClassLoader loader : chain) { + final Map m = classLoader2Transformlet.get(loader); + if (m == null) continue; + + ret.putAll(m); + } + + return ret; + } + + // ======== Extension load util methods ======== + + static Map> loadExtensionInstances( + ClassLoader classLoader, LinkedHashSet instanceClassNames, Class superType, + String foundMsgHead, String failLoadMsgHead + ) { + Map> ret = new HashMap<>(); + + for (final String className : instanceClassNames) { + try { + final Class clazz = classLoader.loadClass(className); + if (!superType.isAssignableFrom(clazz)) { + final String msg = foundMsgHead + className + + " from classloader " + classLoader + + " at location " + getLocationUrlOfClass(clazz) + + ", but NOT subtype of " + superType.getName() + ", ignored!"; + logger.error(msg); + continue; + } + + Object instance = clazz.getDeclaredConstructor().newInstance(); + + final ClassLoader actualClassLoader = instance.getClass().getClassLoader(); + Set set = ret.computeIfAbsent(actualClassLoader, k -> new HashSet<>()); + set.add(superType.cast(instance)); + + final String msg = foundMsgHead + className + + ", and loaded from classloader " + classLoader + + " at location " + getLocationUrlOfClass(clazz); + logger.info(msg); + } catch (ClassNotFoundException e) { + final String msg = failLoadMsgHead + className + " from classloader " + classLoader + ", cause: " + e.toString(); + logger.warn(msg, e); + } catch (IllegalAccessException | InstantiationException | NoSuchMethodException | + InvocationTargetException e) { + final String msg = failLoadMsgHead + className + " from classloader " + classLoader + ", cause: " + e.toString(); + logger.error(msg, e); + } + } + + return ret; + } + + // return: read lines from URL, url strings + @NonNull + static Pair, Set> readLinesFromExtensionFiles( + /* input */ @NonNull Enumeration extensionFiles, + /* input/output, map url string -> content lines */ @NonNull Map> redExtensionFilesHistory + ) { + final LinkedHashSet mergedLines = new LinkedHashSet<>(); + final Set stringUrls = new HashSet<>(); + + while (extensionFiles.hasMoreElements()) { + final URL url = extensionFiles.nextElement(); + + final String urlString = url.toString(); + stringUrls.add(urlString); + + LinkedHashSet lines; + if (redExtensionFilesHistory.containsKey(urlString)) { + lines = redExtensionFilesHistory.get(urlString); + } else { + lines = readLines(url); + + redExtensionFilesHistory.put(urlString, lines); + } + + mergedLines.addAll(lines); + } + + return new Pair<>(mergedLines, stringUrls); + } + + /** + * this method is modified based on {@link ServiceLoader} + */ + @SuppressWarnings("StatementWithEmptyBody") + static LinkedHashSet readLines(URL extensionFile) { + InputStream inputStream = null; + BufferedReader reader = null; + + LinkedHashSet names = new LinkedHashSet<>(); + try { + inputStream = extensionFile.openStream(); + reader = new BufferedReader(new InputStreamReader(inputStream, UTF_8)); + int lineNum = 1; + while ((lineNum = parseLine(extensionFile, reader, lineNum, names)) >= 0) ; + } catch (IOException x) { + logger.error("Error reading configuration file " + extensionFile, x); + } finally { + try { + if (reader != null) reader.close(); + if (inputStream != null) inputStream.close(); + } catch (IOException y) { + logger.warn("Error closing configuration file " + extensionFile, y); + } + } + + return names; + } + + + /** + * this method is modified based on {@link ServiceLoader} + */ + private static int parseLine(URL url, BufferedReader reader, int lineNum, LinkedHashSet names) throws IOException { + String line = reader.readLine(); + if (line == null) { + return -1; + } + + // remove comments that start with `#` + int ci = line.indexOf('#'); + if (ci >= 0) line = line.substring(0, ci); + + line = line.trim(); + + int n = line.length(); + if (n != 0) { + if ((line.indexOf(' ') >= 0) || (line.indexOf('\t') >= 0)) { + logger.error("Illegal syntax " + line + "in configuration file" + url + ", contains space or tab; ignore this line!"); + return lineNum + 1; + } + + int cp = line.codePointAt(0); + if (!Character.isJavaIdentifierStart(cp)) { + logger.error("Illegal extension class name " + line + " in configuration file " + url + "; ignore this line!"); + return lineNum + 1; + } + for (int i = Character.charCount(cp); i < n; i += Character.charCount(cp)) { + cp = line.codePointAt(i); + if (!Character.isJavaIdentifierPart(cp) && (cp != '.')) { + logger.error("Illegal extension class name: " + line + " in configuration file " + url + "; ignore this line!"); + return lineNum + 1; + } + } + + names.add(line); + } + + return lineNum + 1; + } + + static class Pair { + T first; + U second; + + public Pair(T first, U second) { + this.first = first; + this.second = second; + } + } +} diff --git a/ttl-core/src/main/java/com/alibaba/ttl3/threadpool/agent/TtlTransformer.java b/ttl-core/src/main/java/com/alibaba/ttl3/threadpool/agent/TtlTransformer.java new file mode 100644 index 000000000..d4a6e92d6 --- /dev/null +++ b/ttl-core/src/main/java/com/alibaba/ttl3/threadpool/agent/TtlTransformer.java @@ -0,0 +1,99 @@ +package com.alibaba.ttl3.threadpool.agent; + +import com.alibaba.ttl3.threadpool.agent.logging.Logger; +import com.alibaba.ttl3.threadpool.agent.transformlet.ClassInfo; +import com.alibaba.ttl3.threadpool.agent.transformlet.TtlTransformlet; +import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; + +import java.lang.instrument.ClassFileTransformer; +import java.security.ProtectionDomain; +import java.util.ArrayList; +import java.util.List; + +import static com.alibaba.ttl3.threadpool.agent.transformlet.helper.TtlTransformletHelper.isClassUnderPackage; + +/** + * TTL {@link ClassFileTransformer} of Java Agent + * + * @author Jerry Lee (oldratlee at gmail dot com) + * @see ClassFileTransformer + * @see The mechanism for instrumentation + */ +public class TtlTransformer implements ClassFileTransformer { + private static final Logger logger = Logger.getLogger(TtlTransformer.class); + + /** + * "null if no transform is performed", + * see {@code @return} of {@link ClassFileTransformer#transform(ClassLoader, String, Class, ProtectionDomain, byte[])} + */ + @SuppressFBWarnings({"EI_EXPOSE_REP"}) + // [ERROR] com.alibaba.ttl3.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 TtlExtensionTransformletManager extensionTransformletManager; + private final List transformletList = new ArrayList<>(); + private final boolean logClassTransform; + + TtlTransformer(List transformletList, boolean logClassTransform) { + extensionTransformletManager = new TtlExtensionTransformletManager(); + + this.logClassTransform = logClassTransform; + for (TtlTransformlet ttlTransformlet : transformletList) { + this.transformletList.add(ttlTransformlet); + logger.info("[TtlTransformer] add Transformlet " + ttlTransformlet.getClass().getName()); + } + } + + /** + * info about class loader: may be null if the bootstrap loader. + *

+ * more info see {@link ClassFileTransformer#transform(ClassLoader, String, Class, ProtectionDomain, byte[])} + */ + @Override + public final byte[] transform(@Nullable final ClassLoader loader, @Nullable final String classFile, final Class classBeingRedefined, + final ProtectionDomain protectionDomain, @NonNull final byte[] classFileBuffer) { + try { + // Lambda has no class file, no need to transform, just return. + if (classFile == null) return NO_TRANSFORM; + + final ClassInfo classInfo = new ClassInfo(classFile, classFileBuffer, loader); + if (isClassUnderPackage(classInfo.getClassName(), "com.alibaba.ttl")) return NO_TRANSFORM; + if (isClassUnderPackage(classInfo.getClassName(), "java.lang")) return NO_TRANSFORM; + + if (logClassTransform) + logger.info("[TtlTransformer] transforming " + classInfo.getClassName() + + " from classloader " + classInfo.getClassLoader() + + " at location " + classInfo.getLocationUrl()); + + extensionTransformletManager.collectExtensionTransformlet(classInfo); + + for (TtlTransformlet transformlet : transformletList) { + transformlet.doTransform(classInfo); + if (classInfo.isModified()) { + logger.info("[TtlTransformer] " + transformlet.getClass().getName() + " transformed " + classInfo.getClassName() + + " from classloader " + classInfo.getClassLoader() + + " at location " + classInfo.getLocationUrl()); + return classInfo.getCtClass().toBytecode(); + } + } + + final String transformlet = extensionTransformletManager.extensionTransformletDoTransform(classInfo); + if (classInfo.isModified()) { + logger.info("[TtlTransformer] " + transformlet + " transformed " + classInfo.getClassName() + + " from classloader " + classInfo.getClassLoader() + + " at location " + classInfo.getLocationUrl()); + return classInfo.getCtClass().toBytecode(); + } + } catch (Throwable t) { + String msg = "[TtlTransformer] fail to transform class " + classFile + ", cause: " + t.toString(); + logger.error(msg, t); + throw new IllegalStateException(msg, t); + } + + return NO_TRANSFORM; + } +} diff --git a/ttl-core/src/main/java/com/alibaba/ttl3/threadpool/agent/logging/Logger.java b/ttl-core/src/main/java/com/alibaba/ttl3/threadpool/agent/logging/Logger.java new file mode 100644 index 000000000..2cad0ad35 --- /dev/null +++ b/ttl-core/src/main/java/com/alibaba/ttl3/threadpool/agent/logging/Logger.java @@ -0,0 +1,106 @@ +package com.alibaba.ttl3.threadpool.agent.logging; + +import com.alibaba.ttl3.threadpool.agent.TtlAgent; +import com.alibaba.ttl3.threadpool.agent.transformlet.TtlTransformlet; + +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.logging.Level; + +/** + * Logger adaptor for ttl TTL agent/transformlet. Only use for TTL agent/transformlet! + * + * @author Jerry Lee (oldratlee at gmail dot com) + * @see TtlTransformlet + * @see TtlAgent + */ +public abstract class 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; + } + + /** + * Only for test code + */ + public static void setLoggerImplTypeIfNotSetYet(String type) { + if (loggerImplType == -1) setLoggerImplType(type); + } + + 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 void warn(String msg) { + log(Level.WARNING, msg, null); + } + + public void warn(String msg, Throwable thrown) { + log(Level.WARNING, msg, thrown); + } + + public void error(String msg) { + log(Level.SEVERE, msg, null); + } + + public void error(String msg, Throwable thrown) { + log(Level.SEVERE, msg, thrown); + } + + protected abstract void log(Level level, String msg, Throwable thrown); + + private static class StdErrorLogger extends Logger { + StdErrorLogger(Class clazz) { + super(clazz); + } + + @Override + public void log(Level level, String msg, Throwable thrown) { + if (level == Level.SEVERE) { + final String time = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").format(new Date()); + System.err.printf("%s %s [%s] %s: %s%n", time, level, Thread.currentThread().getName(), logClass.getSimpleName(), msg); + if (thrown != null) thrown.printStackTrace(); + } + } + } + + private static class StdOutLogger extends Logger { + StdOutLogger(Class clazz) { + super(clazz); + } + + @Override + public void log(Level level, String msg, Throwable thrown) { + final String time = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").format(new Date()); + System.out.printf("%s %s [%s] %s: %s%n", time, level, Thread.currentThread().getName(), logClass.getSimpleName(), msg); + if (thrown != null) thrown.printStackTrace(System.out); + } + } +} diff --git a/ttl-core/src/main/java/com/alibaba/ttl3/threadpool/agent/logging/package-info.java b/ttl-core/src/main/java/com/alibaba/ttl3/threadpool/agent/logging/package-info.java new file mode 100644 index 000000000..91e0d445d --- /dev/null +++ b/ttl-core/src/main/java/com/alibaba/ttl3/threadpool/agent/logging/package-info.java @@ -0,0 +1,7 @@ +/** + * TTL Agent Logger. Only use for TTL agent/transformlet. + * + * @author Jerry Lee (oldratlee at gmail dot com) + * @see com.alibaba.ttl3.threadpool.agent.logging.Logger + */ +package com.alibaba.ttl3.threadpool.agent.logging; diff --git a/ttl-core/src/main/java/com/alibaba/ttl3/threadpool/agent/package-info.java b/ttl-core/src/main/java/com/alibaba/ttl3/threadpool/agent/package-info.java new file mode 100644 index 000000000..875501ff4 --- /dev/null +++ b/ttl-core/src/main/java/com/alibaba/ttl3/threadpool/agent/package-info.java @@ -0,0 +1,8 @@ +/** + * TTL Agent. + * + * @author Jerry Lee (oldratlee at gmail dot com) + * @see com.alibaba.ttl3.threadpool.agent.TtlAgent + * @see The mechanism for instrumentation + */ +package com.alibaba.ttl3.threadpool.agent; diff --git a/ttl-core/src/main/java/com/alibaba/ttl3/threadpool/agent/transformlet/ClassInfo.java b/ttl-core/src/main/java/com/alibaba/ttl3/threadpool/agent/transformlet/ClassInfo.java new file mode 100644 index 000000000..ca4789f11 --- /dev/null +++ b/ttl-core/src/main/java/com/alibaba/ttl3/threadpool/agent/transformlet/ClassInfo.java @@ -0,0 +1,97 @@ +package com.alibaba.ttl3.threadpool.agent.transformlet; + +import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import javassist.ClassPool; +import javassist.CtClass; +import javassist.LoaderClassPath; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.net.URL; + +import static com.alibaba.ttl3.threadpool.agent.transformlet.helper.TtlTransformletHelper.getLocationUrlOfClass; + +/** + * Class Info for {@link TtlTransformlet}. + * + * Caution:
+ * Do NOT load {@link Class} which is transforming, or the transform will lose effectiveness. + * + * @author Jerry Lee (oldratlee at gmail dot com) + */ +public class ClassInfo { + private final String transformerClassFile; + private final String className; + private final byte[] classFileBuffer; + private final ClassLoader loader; + + // SuppressFBWarnings for classFileBuffer/loader parameter: + // [ERROR] new com.alibaba.ttl3.threadpool.agent.transformlet.ClassInfo(String, byte[], ClassLoader) + // may expose internal representation by storing an externally mutable object + // into ClassInfo.classFileBuffer/loader + public ClassInfo(@NonNull String transformerClassFile, + @NonNull @SuppressFBWarnings({"EI_EXPOSE_REP2"}) byte[] classFileBuffer, + @Nullable @SuppressFBWarnings({"EI_EXPOSE_REP2"}) ClassLoader loader) { + this.transformerClassFile = transformerClassFile; + this.className = toClassName(transformerClassFile); + this.classFileBuffer = classFileBuffer; + this.loader = loader; + } + + @NonNull + public String getClassName() { + return className; + } + + private CtClass ctClass; + + public URL getLocationUrl() throws IOException { + return getLocationUrlOfClass(getCtClass()); + } + + @NonNull + @SuppressFBWarnings({"EI_EXPOSE_REP"}) + // [ERROR] Medium: com.alibaba.ttl3.threadpool.agent.transformlet.ClassInfo.getCtClass() + // may expose internal representation + // by returning ClassInfo.ctClass [com.alibaba.ttl3.threadpool.agent.transformlet.ClassInfo] + 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; + } + + @SuppressFBWarnings({"EI_EXPOSE_REP"}) + // [ERROR] Medium: com.alibaba.ttl3.threadpool.agent.transformlet.ClassInfo.getClassLoader() + // may expose internal representation + // by returning ClassInfo.loader [com.alibaba.ttl3.threadpool.agent.transformlet.ClassInfo] + public ClassLoader getClassLoader() { + return loader; + } + + private static String toClassName(@NonNull final String classFile) { + return classFile.replace('/', '.'); + } +} diff --git a/ttl-core/src/main/java/com/alibaba/ttl3/threadpool/agent/transformlet/TtlTransformlet.java b/ttl-core/src/main/java/com/alibaba/ttl3/threadpool/agent/transformlet/TtlTransformlet.java new file mode 100644 index 000000000..2f28bc4ba --- /dev/null +++ b/ttl-core/src/main/java/com/alibaba/ttl3/threadpool/agent/transformlet/TtlTransformlet.java @@ -0,0 +1,25 @@ +package com.alibaba.ttl3.threadpool.agent.transformlet; + +import com.alibaba.ttl3.threadpool.agent.TtlTransformer; +import edu.umd.cs.findbugs.annotations.NonNull; +import javassist.CannotCompileException; +import javassist.NotFoundException; + +import java.io.IOException; + +/** + * TTL {@code Transformlet}. + * + * @author Jerry Lee (oldratlee at gmail dot com) + */ +public interface TtlTransformlet { + /** + * info about class loader: may be null if the bootstrap loader. + *

+ * more info see {@link java.lang.instrument.ClassFileTransformer#transform(ClassLoader, String, Class, java.security.ProtectionDomain, byte[])} + * + * @see TtlTransformer#transform(ClassLoader, String, Class, java.security.ProtectionDomain, byte[]) + * @see java.lang.instrument.ClassFileTransformer#transform + */ + void doTransform(@NonNull ClassInfo classInfo) throws CannotCompileException, NotFoundException, IOException; +} diff --git a/ttl-core/src/main/java/com/alibaba/ttl3/threadpool/agent/transformlet/helper/AbstractExecutorTtlTransformlet.java b/ttl-core/src/main/java/com/alibaba/ttl3/threadpool/agent/transformlet/helper/AbstractExecutorTtlTransformlet.java new file mode 100644 index 000000000..23c87bd5c --- /dev/null +++ b/ttl-core/src/main/java/com/alibaba/ttl3/threadpool/agent/transformlet/helper/AbstractExecutorTtlTransformlet.java @@ -0,0 +1,179 @@ +package com.alibaba.ttl3.threadpool.agent.transformlet.helper; + +import com.alibaba.ttl3.threadpool.TtlExecutors; +import com.alibaba.ttl3.threadpool.agent.logging.Logger; +import com.alibaba.ttl3.threadpool.agent.transformlet.ClassInfo; +import com.alibaba.ttl3.threadpool.agent.transformlet.TtlTransformlet; +import com.alibaba.ttl3.spi.TtlAttachmentsDelegate; +import com.alibaba.ttl3.threadpool.agent.transformlet.internal.PriorityBlockingQueueTtlTransformlet; +import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import javassist.*; + +import java.io.IOException; +import java.lang.reflect.Modifier; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.Callable; + +import static com.alibaba.ttl3.threadpool.agent.transformlet.helper.TtlTransformletHelper.isClassAtPackageJavaUtil; +import static com.alibaba.ttl3.threadpool.agent.transformlet.helper.TtlTransformletHelper.signatureOfMethod; + +/** + * Abstract {@link TtlTransformlet} for {@link java.util.concurrent.Executor} and its subclass. + * + * @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 + * @see PriorityBlockingQueueTtlTransformlet + */ +public abstract class AbstractExecutorTtlTransformlet implements TtlTransformlet { + protected static final String RUNNABLE_CLASS_NAME = "java.lang.Runnable"; + protected static final String CALLABLE_CLASS_NAME = "java.util.concurrent.Callable"; + + protected static final String TTL_RUNNABLE_CLASS_NAME = "com.alibaba.ttl3.TtlRunnable"; + protected static final String TTL_CALLABLE_CLASS_NAME = "com.alibaba.ttl3.TtlCallable"; + + protected static final String THREAD_FACTORY_CLASS_NAME = "java.util.concurrent.ThreadFactory"; + protected static final String THREAD_POOL_EXECUTOR_CLASS_NAME = "java.util.concurrent.ThreadPoolExecutor"; + + protected final Logger logger = Logger.getLogger(getClass()); + + protected final Set executorClassNames; + protected final boolean disableInheritableForThreadPool; + + private final Map paramTypeNameToDecorateMethodClass = new HashMap<>(); + + /** + * @param executorClassNames the executor class names to be transformed + */ + public AbstractExecutorTtlTransformlet(Set executorClassNames, boolean disableInheritableForThreadPool) { + this.executorClassNames = Collections.unmodifiableSet(executorClassNames); + this.disableInheritableForThreadPool = disableInheritableForThreadPool; + + paramTypeNameToDecorateMethodClass.put(RUNNABLE_CLASS_NAME, TTL_RUNNABLE_CLASS_NAME); + paramTypeNameToDecorateMethodClass.put(CALLABLE_CLASS_NAME, TTL_CALLABLE_CLASS_NAME); + } + + @Override + public final void doTransform(@NonNull final ClassInfo classInfo) throws IOException, NotFoundException, CannotCompileException { + // work-around ClassCircularityError: + // https://github.com/alibaba/transmittable-thread-local/issues/278 + // https://github.com/alibaba/transmittable-thread-local/issues/234 + if (isClassAtPackageJavaUtil(classInfo.getClassName())) return; + + final CtClass clazz = classInfo.getCtClass(); + if (executorClassNames.contains(classInfo.getClassName())) { + for (CtMethod method : clazz.getDeclaredMethods()) { + updateSubmitMethodsOfExecutorClass_decorateToTtlWrapperAndSetAutoWrapperAttachment(method); + } + + if (disableInheritableForThreadPool) updateConstructorDisableInheritable(clazz); + + classInfo.setModified(); + } else { + 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 TtlTransformletHelper#doAutoWrap(Runnable) + * @see TtlTransformletHelper#doAutoWrap(Callable) + */ + @SuppressFBWarnings("VA_FORMAT_STRING_USES_NEWLINE") // [ERROR] Format string should use %n rather than \n + private void updateSubmitMethodsOfExecutorClass_decorateToTtlWrapperAndSetAutoWrapperAttachment(@NonNull 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 (paramTypeNameToDecorateMethodClass.containsKey(paramTypeName)) { + String code = String.format( + // auto decorate to TTL wrapper + "$%d = com.alibaba.ttl3.threadpool.agent.transformlet.helper.TtlTransformletHelper.doAutoWrap($% 0) { + logger.info("insert code before method " + signatureOfMethod(method) + " of class " + + method.getDeclaringClass().getName() + ":\n" + insertCode); + method.insertBefore(insertCode.toString()); + } + } + + /** + * @see TtlExecutors#getDisableInheritableThreadFactory(java.util.concurrent.ThreadFactory) + */ + private void updateConstructorDisableInheritable(@NonNull 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.ttl3.threadpool.TtlExecutors.getDisableInheritableThreadFactory($% 0) { + logger.info("insert code before constructor " + signatureOfMethod(constructor) + " of class " + + constructor.getDeclaringClass().getName() + ": " + insertCode); + constructor.insertBefore(insertCode.toString()); + } + } + } + + /** + * @see TtlAttachmentsDelegate#unwrapIfIsAutoWrapper(Object) + */ + private boolean updateBeforeAndAfterExecuteMethodOfExecutorSubclass(@NonNull 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.ttl3.spi.TtlAttachmentsDelegate.unwrapIfIsAutoWrapper($2);"; + logger.info("insert code before method " + 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.ttl3.spi.TtlAttachmentsDelegate.unwrapIfIsAutoWrapper($1);"; + logger.info("insert code before method " + signatureOfMethod(afterExecute) + " of class " + + afterExecute.getDeclaringClass().getName() + ": " + code); + afterExecute.insertBefore(code); + modified = true; + } catch (NotFoundException e) { + // clazz does not override afterExecute method, do nothing. + } + + return modified; + } +} diff --git a/ttl-core/src/main/java/com/alibaba/ttl3/threadpool/agent/transformlet/helper/TtlTransformletHelper.java b/ttl-core/src/main/java/com/alibaba/ttl3/threadpool/agent/transformlet/helper/TtlTransformletHelper.java new file mode 100644 index 000000000..1b594490c --- /dev/null +++ b/ttl-core/src/main/java/com/alibaba/ttl3/threadpool/agent/transformlet/helper/TtlTransformletHelper.java @@ -0,0 +1,212 @@ +package com.alibaba.ttl3.threadpool.agent.transformlet.helper; + +import com.alibaba.ttl3.TtlCallable; +import com.alibaba.ttl3.TtlRunnable; +import com.alibaba.ttl3.spi.TtlEnhanced; +import com.alibaba.ttl3.threadpool.agent.logging.Logger; +import com.alibaba.ttl3.threadpool.agent.transformlet.TtlTransformlet; +import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; +import javassist.*; + +import java.lang.reflect.Modifier; +import java.net.URL; +import java.security.CodeSource; +import java.security.ProtectionDomain; +import java.util.concurrent.Callable; + +import static com.alibaba.ttl3.transmitter.Transmitter.capture; +import static com.alibaba.ttl3.spi.TtlAttachmentsDelegate.setAutoWrapperAttachment; + +/** + * Helper methods for {@link TtlTransformlet} implementation. + * + * @author Jerry Lee (oldratlee at gmail dot com) + */ +public final class TtlTransformletHelper { + private static final Logger logger = Logger.getLogger(TtlTransformletHelper.class); + + // ======== Javassist/Class Helper ======== + + /** + * Output 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 + */ + @NonNull + public static String signatureOfMethod(@NonNull 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(); + } + + public static URL getLocationUrlOfClass(CtClass clazz) { + try { + // proxy classes is dynamic, no class file + if (clazz.getName().startsWith("com.sun.proxy.")) return null; + + return clazz.getURL(); + } catch (Exception e) { + logger.warn("Fail to getLocationUrlOfClass " + clazz.getName() + ", cause: " + e.toString()); + return null; + } + } + + public static String getLocationFileOfClass(CtClass clazz) { + final URL location = getLocationUrlOfClass(clazz); + if (location == null) return null; + + return location.getFile(); + } + + public static URL getLocationUrlOfClass(Class clazz) { + try { + // proxy classes is dynamic, no class file + if (clazz.getName().startsWith("com.sun.proxy.")) return null; + + final ProtectionDomain protectionDomain = clazz.getProtectionDomain(); + if (protectionDomain == null) return null; + + final CodeSource codeSource = protectionDomain.getCodeSource(); + if (codeSource == null) return null; + + return codeSource.getLocation(); + } catch (Exception e) { + logger.warn("Fail to getLocationUrlOfClass " + clazz.getName() + ", cause: " + e.toString()); + return null; + } + } + + public static String getLocationFileOfClass(Class clazz) { + final URL location = getLocationUrlOfClass(clazz); + if (location == null) return null; + + return location.getFile(); + } + + // ======== Method Transform Helper ======== + + @NonNull + public static String renamedMethodNameByTtl(@NonNull CtMethod method) { + return "original$" + method.getName() + "$method$renamed$by$ttl"; + } + + public static String addTryFinallyToMethod(@NonNull CtMethod method, @NonNull String beforeCode, @NonNull String finallyCode) throws CannotCompileException, NotFoundException { + return addTryFinallyToMethod(method, renamedMethodNameByTtl(method), beforeCode, finallyCode); + } + + /** + * Add {@code try-finally} logic to method. + * + * @return the body code of method rewritten + */ + public static String addTryFinallyToMethod(@NonNull CtMethod method, @NonNull String nameForOriginalMethod, @NonNull String beforeCode, @NonNull 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) + newMethod.setName(nameForOriginalMethod); + newMethod.setModifiers(newMethod.getModifiers() + & ~Modifier.PUBLIC /* remove public */ + & ~Modifier.PROTECTED /* remove protected */ + | Modifier.PRIVATE /* add private */); + clazz.addMethod(newMethod); + + 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 + nameForOriginalMethod + "($$);\n" + + "} finally {\n" + + " " + finallyCode + "\n" + + "} }"; + method.setBody(code); + + return code; + } + + // ======== CRR Helper ======== + + @Nullable + public static Object doCaptureIfNotTtlEnhanced(@Nullable Object obj) { + if (obj instanceof TtlEnhanced) return null; + else return capture(); + } + + + // FIXME hard-coded for type Runnable, not generic! + @Nullable + public static Runnable doAutoWrap(@Nullable final Runnable runnable) { + if (runnable == null) return null; + + final TtlRunnable ret = TtlRunnable.get(runnable, false, true); + + // have been auto wrapped? + if (ret != runnable) setAutoWrapperAttachment(ret); + + return ret; + } + + // FIXME hard-coded for type Callable, not generic! + @Nullable + public static Callable doAutoWrap(@Nullable final Callable callable) { + if (callable == null) return null; + + final TtlCallable ret = TtlCallable.get(callable, false, true); + + // have been auto wrapped? + if (ret != callable) setAutoWrapperAttachment(ret); + + return ret; + } + + // ======== class/package info Helper ======== + + @NonNull + public static String getPackageName(@NonNull String className) { + final int idx = className.lastIndexOf('.'); + if (-1 == idx) return ""; + + return className.substring(0, idx); + } + + public static boolean isClassAtPackage(@NonNull String className, @NonNull String packageName) { + return packageName.equals(getPackageName(className)); + } + + public static boolean isClassUnderPackage(@NonNull String className, @NonNull String packageName) { + String packageOfClass = getPackageName(className); + return packageOfClass.equals(packageName) || packageOfClass.startsWith(packageName + "."); + } + + public static boolean isClassAtPackageJavaUtil(@NonNull String className) { + return isClassAtPackage(className, "java.util"); + } + + private TtlTransformletHelper() { + throw new InstantiationError("Must not instantiate this class"); + } +} diff --git a/ttl-core/src/main/java/com/alibaba/ttl3/threadpool/agent/transformlet/helper/package-info.java b/ttl-core/src/main/java/com/alibaba/ttl3/threadpool/agent/transformlet/helper/package-info.java new file mode 100644 index 000000000..01508add3 --- /dev/null +++ b/ttl-core/src/main/java/com/alibaba/ttl3/threadpool/agent/transformlet/helper/package-info.java @@ -0,0 +1,7 @@ +/** + * Helper API for TTL Agent extension {@code Transformlet} development. + * + * @author Jerry Lee (oldratlee at gmail dot com) + * @see com.alibaba.ttl3.threadpool.agent.transformlet.TtlTransformlet + */ +package com.alibaba.ttl3.threadpool.agent.transformlet.helper; diff --git a/ttl-core/src/main/java/com/alibaba/ttl3/threadpool/agent/transformlet/internal/ForkJoinTtlTransformlet.java b/ttl-core/src/main/java/com/alibaba/ttl3/threadpool/agent/transformlet/internal/ForkJoinTtlTransformlet.java new file mode 100644 index 000000000..3252dc288 --- /dev/null +++ b/ttl-core/src/main/java/com/alibaba/ttl3/threadpool/agent/transformlet/internal/ForkJoinTtlTransformlet.java @@ -0,0 +1,92 @@ +package com.alibaba.ttl3.threadpool.agent.transformlet.internal; + +import com.alibaba.ttl3.spi.TtlEnhanced; +import com.alibaba.ttl3.threadpool.agent.TtlAgent; +import com.alibaba.ttl3.threadpool.agent.logging.Logger; +import com.alibaba.ttl3.threadpool.agent.transformlet.ClassInfo; +import com.alibaba.ttl3.threadpool.agent.transformlet.TtlTransformlet; +import com.alibaba.ttl3.threadpool.agent.transformlet.helper.TtlTransformletHelper; +import edu.umd.cs.findbugs.annotations.NonNull; +import javassist.*; + +import java.io.IOException; + +import static com.alibaba.ttl3.threadpool.agent.transformlet.helper.TtlTransformletHelper.*; + +/** + * {@link TtlTransformlet} 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 + */ +public final class ForkJoinTtlTransformlet implements TtlTransformlet { + private static final Logger logger = Logger.getLogger(ForkJoinTtlTransformlet.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 ForkJoinTtlTransformlet() { + this.disableInheritableForThreadPool = TtlAgent.isDisableInheritableForThreadPool(); + } + + @Override + public void doTransform(@NonNull 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 TtlTransformletHelper#doCaptureIfNotTtlEnhanced(Object) + */ + private void updateForkJoinTaskClass(@NonNull 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.ttl3.threadpool.agent.transformlet.helper.TtlTransformletHelper.doCaptureIfNotTtlEnhanced(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 = renamedMethodNameByTtl(doExecMethod); + + final String beforeCode = "if (this instanceof " + TtlEnhanced.class.getName() + ") {\n" + // if the class is already TTL enhanced(eg: com.alibaba.ttl3.TtlRecursiveTask) + " return " + doExec_renamed_method_name + "($$);\n" + // return directly/do nothing + "}\n" + + "Object backup = com.alibaba.ttl3.transmitter.Transmitter.replay(" + capturedFieldName + ");"; + + final String finallyCode = "com.alibaba.ttl3.transmitter.Transmitter.restore(backup);"; + + final String code = addTryFinallyToMethod(doExecMethod, doExec_renamed_method_name, beforeCode, finallyCode); + logger.info("insert code around method " + signatureOfMethod(doExecMethod) + " of class " + clazz.getName() + ": " + code); + } + + private void updateConstructorDisableInheritable(@NonNull 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.ttl3.threadpool.TtlForkJoinPoolHelper.getDisableInheritableForkJoinWorkerThreadFactory($% 0) { + logger.info("insert code before method " + signatureOfMethod(constructor) + " of class " + + constructor.getDeclaringClass().getName() + ": " + insertCode); + constructor.insertBefore(insertCode.toString()); + } + } + } +} diff --git a/ttl-core/src/main/java/com/alibaba/ttl3/threadpool/agent/transformlet/internal/JdkExecutorTtlTransformlet.java b/ttl-core/src/main/java/com/alibaba/ttl3/threadpool/agent/transformlet/internal/JdkExecutorTtlTransformlet.java new file mode 100644 index 000000000..e9b93ddb5 --- /dev/null +++ b/ttl-core/src/main/java/com/alibaba/ttl3/threadpool/agent/transformlet/internal/JdkExecutorTtlTransformlet.java @@ -0,0 +1,33 @@ +package com.alibaba.ttl3.threadpool.agent.transformlet.internal; + +import com.alibaba.ttl3.threadpool.agent.TtlAgent; +import com.alibaba.ttl3.threadpool.agent.transformlet.TtlTransformlet; +import com.alibaba.ttl3.threadpool.agent.transformlet.helper.AbstractExecutorTtlTransformlet; + +import java.util.HashSet; +import java.util.Set; + +/** + * {@link TtlTransformlet} for {@link java.util.concurrent.ThreadPoolExecutor} + * and {@link java.util.concurrent.ScheduledThreadPoolExecutor}. + * + * @author Jerry Lee (oldratlee at gmail dot com) + * @author wuwen5 (wuwen.55 at aliyun dot com) + * @see java.util.concurrent.ThreadPoolExecutor + * @see java.util.concurrent.ScheduledThreadPoolExecutor + */ +public final class JdkExecutorTtlTransformlet extends AbstractExecutorTtlTransformlet implements TtlTransformlet { + + private static Set getExecutorClassNames() { + Set executorClassNames = new HashSet<>(); + + executorClassNames.add(THREAD_POOL_EXECUTOR_CLASS_NAME); + executorClassNames.add("java.util.concurrent.ScheduledThreadPoolExecutor"); + + return executorClassNames; + } + + public JdkExecutorTtlTransformlet() { + super(getExecutorClassNames(), TtlAgent.isDisableInheritableForThreadPool()); + } +} diff --git a/ttl-core/src/main/java/com/alibaba/ttl3/threadpool/agent/transformlet/internal/PriorityBlockingQueueTtlTransformlet.java b/ttl-core/src/main/java/com/alibaba/ttl3/threadpool/agent/transformlet/internal/PriorityBlockingQueueTtlTransformlet.java new file mode 100644 index 000000000..273fc21c4 --- /dev/null +++ b/ttl-core/src/main/java/com/alibaba/ttl3/threadpool/agent/transformlet/internal/PriorityBlockingQueueTtlTransformlet.java @@ -0,0 +1,119 @@ +package com.alibaba.ttl3.threadpool.agent.transformlet.internal; + +import com.alibaba.ttl3.threadpool.TtlExecutors; +import com.alibaba.ttl3.threadpool.agent.logging.Logger; +import com.alibaba.ttl3.threadpool.agent.transformlet.ClassInfo; +import com.alibaba.ttl3.threadpool.agent.transformlet.TtlTransformlet; +import edu.umd.cs.findbugs.annotations.NonNull; +import javassist.CannotCompileException; +import javassist.CtClass; +import javassist.CtConstructor; +import javassist.NotFoundException; + +import java.io.IOException; +import java.util.Comparator; + +import static com.alibaba.ttl3.threadpool.agent.transformlet.helper.TtlTransformletHelper.signatureOfMethod; + +/** + * TTL {@link TtlTransformlet} for {@link java.util.concurrent.PriorityBlockingQueue PriorityBlockingQueue}. + *

+ * Avoid {@code ClassCastException(TtlRunnable cannot be cast to Comparable)} problem + * for combination usage: + *

    + *
  • use {@link java.util.concurrent.PriorityBlockingQueue PriorityBlockingQueue} for {@link java.util.concurrent.ThreadPoolExecutor ThreadPoolExecutor}
  • + *
  • use {@code TTL Agent} {@link JdkExecutorTtlTransformlet}
  • + *
+ * More info see issue #330 + * + * @author Jerry Lee (oldratlee at gmail dot com) + * @see TtlExecutors#getTtlRunnableUnwrapComparator(Comparator) + * @see TtlExecutors#getTtlRunnableUnwrapComparatorForComparableRunnable() + * @see java.util.concurrent.ThreadPoolExecutor + * @see java.util.concurrent.ThreadPoolExecutor#ThreadPoolExecutor(int, int, long, java.util.concurrent.TimeUnit, java.util.concurrent.BlockingQueue) + * @see java.util.concurrent.PriorityBlockingQueue + * @see java.util.concurrent.PriorityBlockingQueue#PriorityBlockingQueue(int, Comparator) + * @see java.util.PriorityQueue + * @see java.util.PriorityQueue#PriorityQueue(int, Comparator) + * @see JdkExecutorTtlTransformlet + */ +public class PriorityBlockingQueueTtlTransformlet implements TtlTransformlet { + private static final Logger logger = Logger.getLogger(PriorityBlockingQueueTtlTransformlet.class); + + private static final String PRIORITY_BLOCKING_QUEUE_CLASS_NAME = "java.util.concurrent.PriorityBlockingQueue"; + private static final String PRIORITY_QUEUE_CLASS_NAME = "java.util.PriorityQueue"; + private static final String COMPARATOR_FIELD_NAME = "comparator"; + + @Override + public void doTransform(@NonNull ClassInfo classInfo) throws IOException, CannotCompileException, NotFoundException { + final String className = classInfo.getClassName(); + + if (PRIORITY_BLOCKING_QUEUE_CLASS_NAME.equals(className)) { + updatePriorityBlockingQueueClass(classInfo.getCtClass()); + classInfo.setModified(); + } + + if (PRIORITY_QUEUE_CLASS_NAME.equals(className)) { + updateBlockingQueueClass(classInfo.getCtClass()); + classInfo.setModified(); + } + } + + private void updatePriorityBlockingQueueClass(@NonNull final CtClass clazz) throws CannotCompileException, NotFoundException { + if (!haveComparatorField(clazz)) { + // In Java 6, PriorityBlockingQueue implementation do not have field comparator, + // need transform more fundamental class PriorityQueue + logger.info(PRIORITY_BLOCKING_QUEUE_CLASS_NAME + " do not have field " + COMPARATOR_FIELD_NAME + + ", transform " + PRIORITY_QUEUE_CLASS_NAME + " instead."); + return; + } + + modifyConstructors(clazz); + } + + private void updateBlockingQueueClass(@NonNull final CtClass clazz) throws CannotCompileException, NotFoundException { + final CtClass classPriorityBlockingQueue = clazz.getClassPool().getCtClass(PRIORITY_BLOCKING_QUEUE_CLASS_NAME); + if (haveComparatorField(classPriorityBlockingQueue)) return; + + logger.info(PRIORITY_BLOCKING_QUEUE_CLASS_NAME + " do not have field " + COMPARATOR_FIELD_NAME + + ", so need transform " + PRIORITY_QUEUE_CLASS_NAME); + modifyConstructors(clazz); + } + + private static boolean haveComparatorField(CtClass clazz) { + try { + clazz.getDeclaredField(COMPARATOR_FIELD_NAME); + return true; + } catch (NotFoundException e) { + return false; + } + } + + /** + * wrap comparator field in constructors + */ + private static final String CODE = "this." + COMPARATOR_FIELD_NAME + " = " + + PriorityBlockingQueueTtlTransformlet.class.getName() + + ".overwriteComparatorField$by$ttl(this." + COMPARATOR_FIELD_NAME + ");"; + + /** + * @see #overwriteComparatorField$by$ttl(Comparator) + */ + private static void modifyConstructors(@NonNull CtClass clazz) throws NotFoundException, CannotCompileException { + for (CtConstructor constructor : clazz.getDeclaredConstructors()) { + logger.info("insert code after constructor " + signatureOfMethod(constructor) + " of class " + + constructor.getDeclaringClass().getName() + ": " + CODE); + + constructor.insertAfter(CODE); + } + } + + /** + * @see TtlExecutors#getTtlRunnableUnwrapComparator(Comparator) + */ + public static Comparator overwriteComparatorField$by$ttl(Comparator comparator) { + if (comparator == null) return TtlExecutors.getTtlRunnableUnwrapComparatorForComparableRunnable(); + + return TtlExecutors.getTtlRunnableUnwrapComparator(comparator); + } +} diff --git a/ttl-core/src/main/java/com/alibaba/ttl3/threadpool/agent/transformlet/internal/TimerTaskTtlTransformlet.java b/ttl-core/src/main/java/com/alibaba/ttl3/threadpool/agent/transformlet/internal/TimerTaskTtlTransformlet.java new file mode 100644 index 000000000..41dff553f --- /dev/null +++ b/ttl-core/src/main/java/com/alibaba/ttl3/threadpool/agent/transformlet/internal/TimerTaskTtlTransformlet.java @@ -0,0 +1,77 @@ +package com.alibaba.ttl3.threadpool.agent.transformlet.internal; + +import com.alibaba.ttl3.threadpool.agent.logging.Logger; +import com.alibaba.ttl3.threadpool.agent.transformlet.ClassInfo; +import com.alibaba.ttl3.threadpool.agent.transformlet.TtlTransformlet; +import com.alibaba.ttl3.threadpool.agent.transformlet.helper.TtlTransformletHelper; +import edu.umd.cs.findbugs.annotations.NonNull; +import javassist.*; + +import java.io.IOException; + +import static com.alibaba.ttl3.threadpool.agent.transformlet.helper.TtlTransformletHelper.*; + +/** + * {@link TtlTransformlet} 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 + */ +public final class TimerTaskTtlTransformlet implements TtlTransformlet { + private static final Logger logger = Logger.getLogger(TimerTaskTtlTransformlet.class); + + private static final String TIMER_TASK_CLASS_NAME = "java.util.TimerTask"; + private static final String RUN_METHOD_NAME = "run"; + + @Override + public void doTransform(@NonNull final ClassInfo classInfo) throws IOException, NotFoundException, CannotCompileException { + // work-around ClassCircularityError: + if (isClassAtPackageJavaUtil(classInfo.getClassName())) return; + + // TimerTask class is checked by above logic. + // + // 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 TtlTransformletHelper#doCaptureIfNotTtlEnhanced(Object) + */ + private void updateTimerTaskClass(@NonNull 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.ttl3.threadpool.agent.transformlet.helper.TtlTransformletHelper.doCaptureIfNotTtlEnhanced(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.ttl3.transmitter.Transmitter.replay(" + capturedFieldName + ");"; + final String finallyCode = "com.alibaba.ttl3.transmitter.Transmitter.restore(backup);"; + + final String code = addTryFinallyToMethod(runMethod, beforeCode, finallyCode); + logger.info("insert code around method " + signatureOfMethod(runMethod) + " of class " + clazz.getName() + ": " + code); + } +} diff --git a/ttl-core/src/main/java/com/alibaba/ttl3/threadpool/agent/transformlet/internal/package-info.java b/ttl-core/src/main/java/com/alibaba/ttl3/threadpool/agent/transformlet/internal/package-info.java new file mode 100644 index 000000000..176a0a6b5 --- /dev/null +++ b/ttl-core/src/main/java/com/alibaba/ttl3/threadpool/agent/transformlet/internal/package-info.java @@ -0,0 +1,10 @@ +/** + * TTL built-in {@code Transformlet} implementations. + * + * @author Jerry Lee (oldratlee at gmail dot com) + * @see com.alibaba.ttl3.threadpool.agent.transformlet.internal.JdkExecutorTtlTransformlet + * @see com.alibaba.ttl3.threadpool.agent.transformlet.internal.ForkJoinTtlTransformlet + * @see com.alibaba.ttl3.threadpool.agent.transformlet.internal.TimerTaskTtlTransformlet + * @see com.alibaba.ttl3.threadpool.agent.transformlet.TtlTransformlet + */ +package com.alibaba.ttl3.threadpool.agent.transformlet.internal; diff --git a/ttl-core/src/main/java/com/alibaba/ttl3/threadpool/agent/transformlet/package-info.java b/ttl-core/src/main/java/com/alibaba/ttl3/threadpool/agent/transformlet/package-info.java new file mode 100644 index 000000000..d93cb915c --- /dev/null +++ b/ttl-core/src/main/java/com/alibaba/ttl3/threadpool/agent/transformlet/package-info.java @@ -0,0 +1,9 @@ +/** + * TTL {@code Transformlet} API for TTL Agent extension {@code Transformlet} development. + *

+ * TTL built-in {@code Transformlet} implementations is in the package {@link com.alibaba.ttl3.threadpool.agent.transformlet.internal}. + * + * @author Jerry Lee (oldratlee at gmail dot com) + * @see com.alibaba.ttl3.threadpool.agent.transformlet.TtlTransformlet + */ +package com.alibaba.ttl3.threadpool.agent.transformlet; diff --git a/ttl-core/src/main/java/com/alibaba/ttl3/threadpool/package-info.java b/ttl-core/src/main/java/com/alibaba/ttl3/threadpool/package-info.java new file mode 100644 index 000000000..0080f0681 --- /dev/null +++ b/ttl-core/src/main/java/com/alibaba/ttl3/threadpool/package-info.java @@ -0,0 +1,7 @@ +/** + * Thread pool wrap/decoration utils. + * + * @author Jerry Lee (oldratlee at gmail dot com) + * @see com.alibaba.ttl3.threadpool.TtlExecutors + */ +package com.alibaba.ttl3.threadpool; diff --git a/ttl-core/src/main/java/com/alibaba/ttl3/transmitter/ThreadLocalTransmitRegister.java b/ttl-core/src/main/java/com/alibaba/ttl3/transmitter/ThreadLocalTransmitRegister.java new file mode 100644 index 000000000..8bc73718d --- /dev/null +++ b/ttl-core/src/main/java/com/alibaba/ttl3/transmitter/ThreadLocalTransmitRegister.java @@ -0,0 +1,253 @@ +package com.alibaba.ttl3.transmitter; + +import com.alibaba.ttl3.TransmittableThreadLocal; +import com.alibaba.ttl3.TtlCopier; +import edu.umd.cs.findbugs.annotations.NonNull; + +import java.util.HashMap; +import java.util.Map; +import java.util.WeakHashMap; +import java.util.logging.Logger; + +/** + * ThreadLocalTransmitRegister, ThreadLocal Integration. + *

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

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

{@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);}
+ *

+ * The fields stored the {@code ThreadLocal} instances are generally {@code private static}, + * so the {@code ThreadLocal} instances need be got by reflection, for example: + * + *

+ * Field field = TheClassStoredThreadLocal.class.getDeclaredField(staticFieldName);
+ * field.setAccessible(true);
+ * {@code @SuppressWarnings("unchecked")}
+ * {@code ThreadLocal} threadLocal = {@code (ThreadLocal)} field.get(null);
+ * + * Caution:
+ * If the registered {@link ThreadLocal} instance is not {@link InheritableThreadLocal}, + * the instance can NOT {@code inherit} value from parent thread(aka. the inheritable ability)! + * + * @author Jerry Lee (oldratlee at gmail dot com) + */ +public final class ThreadLocalTransmitRegister { + private static final Logger logger = Logger.getLogger(ThreadLocalTransmitRegister.class.getName()); + + private static volatile WeakHashMap, TtlCopier> threadLocalHolder = new WeakHashMap<>(); + + private static final Object threadLocalHolderUpdateLock = new Object(); + + private static final TtlCopier shadowCopier = parentValue -> parentValue; + + /** + * Register the {@link ThreadLocal}(including subclass {@link InheritableThreadLocal}) instances + * to enhance the Transmittable ability for the existed {@link ThreadLocal} instances. + *

+ * If the registered {@link ThreadLocal} instance is {@link TransmittableThreadLocal} just ignores and return {@code true}. + * since a {@link TransmittableThreadLocal} instance itself has the {@code Transmittable} ability, + * it is unnecessary to register a {@link TransmittableThreadLocal} instance. + *

+ * Caution:
+ * If the registered {@link ThreadLocal} instance is not {@link InheritableThreadLocal}, + * the instance can NOT {@code inherit} value from parent thread(aka. the inheritable ability)! + * + * @param threadLocal the {@link ThreadLocal} instance that to enhance the Transmittable ability + * @param copier the {@link TtlCopier} + * @return {@code true} if register the {@link ThreadLocal} instance and set {@code copier}, otherwise {@code false} + * @see #registerThreadLocal(ThreadLocal, TtlCopier, boolean) + */ + public static boolean registerThreadLocal(@NonNull ThreadLocal threadLocal, @NonNull TtlCopier copier) { + return registerThreadLocal(threadLocal, copier, false); + } + + /** + * Register the {@link ThreadLocal}(including subclass {@link InheritableThreadLocal}) instances + * to enhance the Transmittable ability for the existed {@link ThreadLocal} instances. + *

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

+ * If the registered {@link ThreadLocal} instance is {@link TransmittableThreadLocal} just ignores and return {@code true}. + * since a {@link TransmittableThreadLocal} instance itself has the {@code Transmittable} ability, + * it is unnecessary to register a {@link TransmittableThreadLocal} instance. + *

+ * Caution:
+ * If the registered {@link ThreadLocal} instance is not {@link InheritableThreadLocal}, + * the instance can NOT {@code inherit} value from parent thread(aka. the inheritable ability)! + * + * @param threadLocal the {@link ThreadLocal} instance that to enhance the Transmittable ability + * @return {@code true} if register the {@link ThreadLocal} instance and set {@code copier}, otherwise {@code false} + * @see #registerThreadLocal(ThreadLocal, TtlCopier) + * @see #registerThreadLocal(ThreadLocal, TtlCopier, boolean) + */ + @SuppressWarnings("unchecked") + public static boolean registerThreadLocalWithShadowCopier(@NonNull ThreadLocal threadLocal) { + return registerThreadLocal(threadLocal, (TtlCopier) shadowCopier, false); + } + + /** + * Register the {@link ThreadLocal}(including subclass {@link InheritableThreadLocal}) instances + * to enhance the Transmittable ability for the existed {@link ThreadLocal} instances. + *

+ * If the registered {@link ThreadLocal} instance is {@link TransmittableThreadLocal} just ignores and return {@code true}. + * since a {@link TransmittableThreadLocal} instance itself has the {@code Transmittable} ability, + * it is unnecessary to register a {@link TransmittableThreadLocal} instance. + *

+ * Caution:
+ * If the registered {@link ThreadLocal} instance is not {@link InheritableThreadLocal}, + * the instance can NOT {@code inherit} value from parent thread(aka. the inheritable ability)! + * + * @param threadLocal the {@link ThreadLocal} instance that to enhance the Transmittable ability + * @param copier the {@link TtlCopier} + * @param force if {@code true}, update {@code copier} to {@link ThreadLocal} instance + * when a {@link ThreadLocal} instance is already registered; otherwise, ignore. + * @return {@code true} if register the {@link ThreadLocal} instance and set {@code copier}, otherwise {@code false} + * @see #registerThreadLocal(ThreadLocal, TtlCopier) + */ + @SuppressWarnings("unchecked") + public static boolean registerThreadLocal(@NonNull ThreadLocal threadLocal, @NonNull TtlCopier copier, boolean force) { + if (threadLocal instanceof TransmittableThreadLocal) { + logger.warning("register a TransmittableThreadLocal instance, this is unnecessary!"); + return true; + } + + synchronized (threadLocalHolderUpdateLock) { + if (!force && threadLocalHolder.containsKey(threadLocal)) return false; + + WeakHashMap, TtlCopier> newHolder = new WeakHashMap<>(threadLocalHolder); + newHolder.put((ThreadLocal) threadLocal, (TtlCopier) copier); + threadLocalHolder = newHolder; + return true; + } + } + + /** + * Register the {@link ThreadLocal}(including subclass {@link InheritableThreadLocal}) instances + * to enhance the Transmittable ability for the existed {@link ThreadLocal} instances. + *

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

+ * If the registered {@link ThreadLocal} instance is {@link TransmittableThreadLocal} just ignores and return {@code true}. + * since a {@link TransmittableThreadLocal} instance itself has the {@code Transmittable} ability, + * it is unnecessary to register a {@link TransmittableThreadLocal} instance. + *

+ * Caution:
+ * If the registered {@link ThreadLocal} instance is not {@link InheritableThreadLocal}, + * the instance can NOT {@code inherit} value from parent thread(aka. the inheritable ability)! + * + * @param threadLocal the {@link ThreadLocal} instance that to enhance the Transmittable ability + * @param force if {@code true}, update {@code copier} to {@link ThreadLocal} instance + * when a {@link ThreadLocal} instance is already registered; otherwise, ignore. + * @return {@code true} if register the {@link ThreadLocal} instance and set {@code copier}, otherwise {@code false} + * @see #registerThreadLocal(ThreadLocal, TtlCopier) + * @see #registerThreadLocal(ThreadLocal, TtlCopier, boolean) + */ + @SuppressWarnings("unchecked") + public static boolean registerThreadLocalWithShadowCopier(@NonNull ThreadLocal threadLocal, boolean force) { + return registerThreadLocal(threadLocal, (TtlCopier) shadowCopier, force); + } + + /** + * Unregister the {@link ThreadLocal} instances + * to remove the Transmittable ability for the {@link ThreadLocal} instances. + *

+ * If the {@link ThreadLocal} instance is {@link TransmittableThreadLocal} just ignores and return {@code true}. + * + * @see #registerThreadLocal(ThreadLocal, TtlCopier) + * @see #registerThreadLocalWithShadowCopier(ThreadLocal) + */ + public static boolean unregisterThreadLocal(@NonNull ThreadLocal threadLocal) { + if (threadLocal instanceof TransmittableThreadLocal) { + logger.warning("unregister a TransmittableThreadLocal instance, this is unnecessary!"); + return true; + } + + synchronized (threadLocalHolderUpdateLock) { + if (!threadLocalHolder.containsKey(threadLocal)) return false; + + WeakHashMap, TtlCopier> newHolder = new WeakHashMap<>(threadLocalHolder); + newHolder.remove(threadLocal); + threadLocalHolder = newHolder; + return true; + } + } + + private static class ThreadLocalTransmittee implements Transmittee, Object>, HashMap, Object>> { + private static final Object threadLocalClearMark = new Object(); + + @NonNull + @Override + public HashMap, Object> capture() { + final HashMap, Object> threadLocal2Value = new HashMap<>(threadLocalHolder.size()); + for (Map.Entry, TtlCopier> entry : threadLocalHolder.entrySet()) { + final ThreadLocal threadLocal = entry.getKey(); + final TtlCopier copier = entry.getValue(); + + threadLocal2Value.put(threadLocal, copier.copy(threadLocal.get())); + } + return threadLocal2Value; + } + + @NonNull + @Override + public HashMap, Object> replay(@NonNull HashMap, Object> captured) { + final HashMap, Object> backup = new HashMap<>(captured.size()); + + for (Map.Entry, Object> entry : captured.entrySet()) { + final ThreadLocal threadLocal = entry.getKey(); + backup.put(threadLocal, threadLocal.get()); + + final Object value = entry.getValue(); + if (value == threadLocalClearMark) threadLocal.remove(); + else threadLocal.set(value); + } + + return backup; + } + + @NonNull + @Override + public HashMap, Object> clear() { + final HashMap, Object> threadLocal2Value = new HashMap<>(threadLocalHolder.size()); + + for (Map.Entry, TtlCopier> entry : threadLocalHolder.entrySet()) { + final ThreadLocal threadLocal = entry.getKey(); + threadLocal2Value.put(threadLocal, threadLocalClearMark); + } + + return replay(threadLocal2Value); + } + + @Override + public void restore(@NonNull HashMap, Object> backup) { + for (Map.Entry, Object> entry : backup.entrySet()) { + final ThreadLocal threadLocal = entry.getKey(); + threadLocal.set(entry.getValue()); + } + } + } + + private static final ThreadLocalTransmittee threadLocalTransmittee = new ThreadLocalTransmittee(); + + static { + Transmitter.registerTransmittee(threadLocalTransmittee); + } + + private ThreadLocalTransmitRegister() { + throw new InstantiationError("Must not instantiate this class"); + } +} diff --git a/ttl-core/src/main/java/com/alibaba/ttl3/transmitter/Transmittee.java b/ttl-core/src/main/java/com/alibaba/ttl3/transmitter/Transmittee.java new file mode 100644 index 000000000..c882bb7e5 --- /dev/null +++ b/ttl-core/src/main/java/com/alibaba/ttl3/transmitter/Transmittee.java @@ -0,0 +1,81 @@ +package com.alibaba.ttl3.transmitter; + +import com.alibaba.crr.CrrTransmit; +import edu.umd.cs.findbugs.annotations.NonNull; + +/** + * The transmittee is the extension point for other {@code ThreadLocal}s + * which are registered by {@link Transmitter#registerTransmittee(Transmittee) registerTransmittee} method. + * Transmittance is completed by by methods {@link #capture() capture()} => + * {@link #replay(Object)} replay(Object)} => {@link #restore(Object) restore(Object)} (aka {@code CRR} operations), + * + * @param the transmittee capture data type + * @param the transmittee backup data type + * @author Jerry Lee (oldratlee at gmail dot com) + * @see Transmitter#registerTransmittee(Transmittee) + * @see Transmitter#unregisterTransmittee(Transmittee) + */ +public interface Transmittee extends CrrTransmit { + /** + * Capture. + *

+ * NOTE: + *

    + *
  • do NOT return {@code null}.
  • + *
  • do NOT throw any exceptions, just ignored.
  • + *
+ * + * @return the capture data of transmittee + */ + @NonNull + C capture(); + + /** + * Replay. + *

+ * NOTE: + *

    + *
  • do NOT return {@code null}.
  • + *
  • do NOT throw any exceptions, just ignored.
  • + *
+ * + * @param captured the capture data of transmittee, the return value of method {@link #capture()} + * @return the backup data of transmittee + */ + @NonNull + B replay(@NonNull C captured); + + /** + * Clear. + *

+ * NOTE: + *

    + *
  • do NOT return {@code null}.
  • + *
  • do NOT throw any exceptions, just ignored.
  • + *
+ *

+ * Semantically, the code {@code `B backup = clear();`} is same as {@code `B backup = replay(EMPTY_CAPTURE);`}. + *

+ * The reason for providing this method is: + *

    + *
  1. lead to more readable code
  2. + *
  3. need not provide the constant {@code EMPTY_CAPTURE}.
  4. + *
+ * + * @return the backup data of transmittee + */ + @NonNull + B clear(); + + /** + * Restore. + *

+ * NOTE:
+ * do NOT throw any exceptions, just ignored. + * + * @param backup the backup data of transmittee, the return value of methods {@link #replay(Object)} or {@link #clear()} + * @see #replay(Object) + * @see #clear() + */ + void restore(@NonNull B backup); +} diff --git a/ttl-core/src/main/java/com/alibaba/ttl3/transmitter/Transmitter.java b/ttl-core/src/main/java/com/alibaba/ttl3/transmitter/Transmitter.java new file mode 100644 index 000000000..005be546a --- /dev/null +++ b/ttl-core/src/main/java/com/alibaba/ttl3/transmitter/Transmitter.java @@ -0,0 +1,266 @@ +package com.alibaba.ttl3.transmitter; + +import com.alibaba.crr.composite.Backup; +import com.alibaba.crr.composite.Capture; +import com.alibaba.crr.composite.CompositeCrrTransmit; +import com.alibaba.ttl3.TransmittableThreadLocal; +import com.alibaba.ttl3.TtlCallable; +import com.alibaba.ttl3.TtlRunnable; +import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; + +import java.util.concurrent.Callable; +import java.util.function.Supplier; + +/** + * {@link Transmitter Transmitter} transmit all {@link TransmittableThreadLocal} + * and registered {@link ThreadLocal} values of the current thread to other thread. + *

+ * Transmittance is completed by static methods {@link #capture()} => + * {@link #replay(Capture)} => {@link #restore(Backup)} (aka {@code CRR} operations). + * {@link ThreadLocal} instances are registered via {@link ThreadLocalTransmitRegister}. + *

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

Framework/Middleware integration to TTL transmittance

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

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

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

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

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

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

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

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

ThreadLocal Integration

+ * If you can not rewrite the existed code which use {@link ThreadLocal} to {@link TransmittableThreadLocal}, + * register the {@link ThreadLocal} instances via {@link ThreadLocalTransmitRegister} + * to enhance the Transmittable ability for the existed {@link ThreadLocal} instances. + * + * @author Yang Fang (snoop dot fy at gmail dot com) + * @author Jerry Lee (oldratlee at gmail dot com) + * @see TtlRunnable + * @see TtlCallable + * @see ThreadLocalTransmitRegister + */ +public final class Transmitter { + private static final CompositeCrrTransmit compositeCrrTransmit = new CompositeCrrTransmit(); + + /** + * Capture all {@link TransmittableThreadLocal} and registered {@link ThreadLocal} values in the current thread. + * + * @return the captured {@link TransmittableThreadLocal} values + */ + @NonNull + public static Capture capture() { + return compositeCrrTransmit.capture(); + } + + /** + * 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() + */ + @NonNull + public static Backup replay(@NonNull Capture captured) { + return compositeCrrTransmit.replay(captured); + } + + /** + * 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. + *

+ * Semantically, the code {@code `Backup backup = clear();`} is same as {@code `Backup backup = replay(EMPTY_CAPTURE);`}. + *

+ * The reason for providing this method is: + * + *

    + *
  1. lead to more readable code
  2. + *
  3. need not provide the constant {@code EMPTY_CAPTURE}.
  4. + *
+ * + * @return the backup {@link TransmittableThreadLocal} values before clear + * @see #replay(Capture) + */ + @NonNull + public static Backup clear() { + return compositeCrrTransmit.clear(); + } + + /** + * Restore the backup {@link TransmittableThreadLocal} and + * registered {@link ThreadLocal} values from {@link #replay(Capture)}/{@link #clear()}. + * + * @param backup the backup {@link TransmittableThreadLocal} values from {@link #replay(Capture)}/{@link #clear()} + * @see #replay(Capture) + * @see #clear() + */ + public static void restore(@NonNull Backup backup) { + compositeCrrTransmit.restore(backup); + } + + /** + * Util method for simplifying {@link #replay(Capture)} and {@link #restore(Backup)} operations. + * + * @param captured captured {@link TransmittableThreadLocal} values from other thread from {@link #capture()} + * @param bizLogic biz logic + * @param the return type of biz logic + * @return the return value of biz logic + * @see #capture() + * @see #replay(Capture) + * @see #restore(Backup) + */ + public static R runSupplierWithCaptured(@NonNull Capture captured, @NonNull Supplier bizLogic) { + final Backup backup = replay(captured); + try { + return bizLogic.get(); + } finally { + restore(backup); + } + } + + /** + * Util method for simplifying {@link #clear()} and {@link #restore(Backup)} operations. + * + * @param bizLogic biz logic + * @param the return type of biz logic + * @return the return value of biz logic + * @see #clear() + * @see #restore(Backup) + */ + public static R runSupplierWithClear(@NonNull Supplier bizLogic) { + final Backup backup = clear(); + try { + return bizLogic.get(); + } finally { + restore(backup); + } + } + + /** + * Util method for simplifying {@link #replay(Capture)} and {@link #restore(Backup)} operations. + * + * @param captured captured {@link TransmittableThreadLocal} values from other thread from {@link #capture()} + * @param bizLogic biz logic + * @param the return type of biz logic + * @return the return value of biz logic + * @throws Exception the exception threw by biz logic + * @see #capture() + * @see #replay(Capture) + * @see #restore(Backup) + */ + @SuppressFBWarnings("THROWS_METHOD_THROWS_CLAUSE_BASIC_EXCEPTION") + public static R runCallableWithCaptured(@NonNull Capture captured, @NonNull Callable bizLogic) throws Exception { + final Backup backup = replay(captured); + try { + return bizLogic.call(); + } finally { + restore(backup); + } + } + + /** + * Util method for simplifying {@link #clear()} and {@link #restore(Backup)} )} operations. + * + * @param bizLogic biz logic + * @param the return type of biz logic + * @return the return value of biz logic + * @throws Exception the exception threw by biz logic + * @see #clear() + * @see #restore(Backup) + */ + @SuppressFBWarnings("THROWS_METHOD_THROWS_CLAUSE_BASIC_EXCEPTION") + public static R runCallableWithClear(@NonNull Callable bizLogic) throws Exception { + final Backup backup = clear(); + try { + return bizLogic.call(); + } finally { + restore(backup); + } + } + + /** + * Register the transmittee({@code CRR}), the extension point for other {@code ThreadLocal}. + * + * @param the transmittee capture data type + * @param the transmittee backup data type + * @return true if the input transmittee is not registered + * @see #unregisterTransmittee(Transmittee) + */ + public static boolean registerTransmittee(@NonNull Transmittee transmittee) { + return compositeCrrTransmit.registerCrrTransmit(transmittee); + } + + /** + * Unregister the transmittee({@code CRR}), the extension point for other {@code ThreadLocal}. + * + * @param the transmittee capture data type + * @param the transmittee backup data type + * @return true if the input transmittee is registered + * @see #registerTransmittee(Transmittee) + */ + public static boolean unregisterTransmittee(@NonNull Transmittee transmittee) { + return compositeCrrTransmit.unregisterCrrTransmit(transmittee); + } + + private Transmitter() { + throw new InstantiationError("Must not instantiate this class"); + } +} diff --git a/ttl-core/src/package-list/java/package-list b/ttl-core/src/package-list/java/package-list new file mode 100644 index 000000000..24c17a899 --- /dev/null +++ b/ttl-core/src/package-list/java/package-list @@ -0,0 +1,315 @@ +com.sun.jarsigner +com.sun.java.accessibility.util +com.sun.javadoc +com.sun.jdi +com.sun.jdi.connect +com.sun.jdi.connect.spi +com.sun.jdi.event +com.sun.jdi.request +com.sun.management +com.sun.net.httpserver +com.sun.net.httpserver.spi +com.sun.nio.sctp +com.sun.security.auth +com.sun.security.auth.callback +com.sun.security.auth.login +com.sun.security.auth.module +com.sun.security.jgss +com.sun.source.doctree +com.sun.source.tree +com.sun.source.util +com.sun.tools.attach +com.sun.tools.attach.spi +com.sun.tools.doclets +com.sun.tools.doclets.standard +com.sun.tools.javac +com.sun.tools.javadoc +com.sun.tools.jconsole +java.applet +java.awt +java.awt.color +java.awt.datatransfer +java.awt.desktop +java.awt.dnd +java.awt.event +java.awt.font +java.awt.geom +java.awt.im +java.awt.im.spi +java.awt.image +java.awt.image.renderable +java.awt.print +java.beans +java.beans.beancontext +java.io +java.lang +java.lang.annotation +java.lang.instrument +java.lang.invoke +java.lang.management +java.lang.module +java.lang.ref +java.lang.reflect +java.math +java.net +java.net.spi +java.nio +java.nio.channels +java.nio.channels.spi +java.nio.charset +java.nio.charset.spi +java.nio.file +java.nio.file.attribute +java.nio.file.spi +java.rmi +java.rmi.activation +java.rmi.dgc +java.rmi.registry +java.rmi.server +java.security +java.security.acl +java.security.cert +java.security.interfaces +java.security.spec +java.sql +java.text +java.text.spi +java.time +java.time.chrono +java.time.format +java.time.temporal +java.time.zone +java.util +java.util.concurrent +java.util.concurrent.atomic +java.util.concurrent.locks +java.util.function +java.util.jar +java.util.logging +java.util.prefs +java.util.regex +java.util.spi +java.util.stream +java.util.zip +javafx.animation +javafx.application +javafx.beans +javafx.beans.binding +javafx.beans.property +javafx.beans.property.adapter +javafx.beans.value +javafx.collections +javafx.collections.transformation +javafx.concurrent +javafx.css +javafx.css.converter +javafx.embed.swing +javafx.event +javafx.fxml +javafx.geometry +javafx.print +javafx.scene +javafx.scene.canvas +javafx.scene.chart +javafx.scene.control +javafx.scene.control.cell +javafx.scene.control.skin +javafx.scene.effect +javafx.scene.image +javafx.scene.input +javafx.scene.layout +javafx.scene.media +javafx.scene.paint +javafx.scene.shape +javafx.scene.text +javafx.scene.transform +javafx.scene.web +javafx.stage +javafx.util +javafx.util.converter +javax.accessibility +javax.activation +javax.activity +javax.annotation +javax.annotation.processing +javax.crypto +javax.crypto.interfaces +javax.crypto.spec +javax.imageio +javax.imageio.event +javax.imageio.metadata +javax.imageio.plugins.bmp +javax.imageio.plugins.jpeg +javax.imageio.plugins.tiff +javax.imageio.spi +javax.imageio.stream +javax.jnlp +javax.jws +javax.jws.soap +javax.lang.model +javax.lang.model.element +javax.lang.model.type +javax.lang.model.util +javax.management +javax.management.loading +javax.management.modelmbean +javax.management.monitor +javax.management.openmbean +javax.management.relation +javax.management.remote +javax.management.remote.rmi +javax.management.timer +javax.naming +javax.naming.directory +javax.naming.event +javax.naming.ldap +javax.naming.spi +javax.net +javax.net.ssl +javax.print +javax.print.attribute +javax.print.attribute.standard +javax.print.event +javax.rmi +javax.rmi.CORBA +javax.rmi.ssl +javax.script +javax.security.auth +javax.security.auth.callback +javax.security.auth.kerberos +javax.security.auth.login +javax.security.auth.spi +javax.security.auth.x500 +javax.security.cert +javax.security.sasl +javax.smartcardio +javax.sound.midi +javax.sound.midi.spi +javax.sound.sampled +javax.sound.sampled.spi +javax.sql +javax.sql.rowset +javax.sql.rowset.serial +javax.sql.rowset.spi +javax.swing +javax.swing.border +javax.swing.colorchooser +javax.swing.event +javax.swing.filechooser +javax.swing.plaf +javax.swing.plaf.basic +javax.swing.plaf.metal +javax.swing.plaf.multi +javax.swing.plaf.nimbus +javax.swing.plaf.synth +javax.swing.table +javax.swing.text +javax.swing.text.html +javax.swing.text.html.parser +javax.swing.text.rtf +javax.swing.tree +javax.swing.undo +javax.tools +javax.transaction +javax.transaction.xa +javax.xml +javax.xml.bind +javax.xml.bind.annotation +javax.xml.bind.annotation.adapters +javax.xml.bind.attachment +javax.xml.bind.helpers +javax.xml.bind.util +javax.xml.catalog +javax.xml.crypto +javax.xml.crypto.dom +javax.xml.crypto.dsig +javax.xml.crypto.dsig.dom +javax.xml.crypto.dsig.keyinfo +javax.xml.crypto.dsig.spec +javax.xml.datatype +javax.xml.namespace +javax.xml.parsers +javax.xml.soap +javax.xml.stream +javax.xml.stream.events +javax.xml.stream.util +javax.xml.transform +javax.xml.transform.dom +javax.xml.transform.sax +javax.xml.transform.stax +javax.xml.transform.stream +javax.xml.validation +javax.xml.ws +javax.xml.ws.handler +javax.xml.ws.handler.soap +javax.xml.ws.http +javax.xml.ws.soap +javax.xml.ws.spi +javax.xml.ws.spi.http +javax.xml.ws.wsaddressing +javax.xml.xpath +jdk.dynalink +jdk.dynalink.beans +jdk.dynalink.linker +jdk.dynalink.linker.support +jdk.dynalink.support +jdk.incubator.http +jdk.javadoc.doclet +jdk.jfr +jdk.jfr.consumer +jdk.jshell +jdk.jshell.execution +jdk.jshell.spi +jdk.jshell.tool +jdk.management.cmm +jdk.management.jfr +jdk.management.resource +jdk.nashorn.api.scripting +jdk.nashorn.api.tree +jdk.net +jdk.packager.services +jdk.security.jarsigner +netscape.javascript +org.ietf.jgss +org.omg.CORBA +org.omg.CORBA_2_3 +org.omg.CORBA_2_3.portable +org.omg.CORBA.DynAnyPackage +org.omg.CORBA.ORBPackage +org.omg.CORBA.portable +org.omg.CORBA.TypeCodePackage +org.omg.CosNaming +org.omg.CosNaming.NamingContextExtPackage +org.omg.CosNaming.NamingContextPackage +org.omg.Dynamic +org.omg.DynamicAny +org.omg.DynamicAny.DynAnyFactoryPackage +org.omg.DynamicAny.DynAnyPackage +org.omg.IOP +org.omg.IOP.CodecFactoryPackage +org.omg.IOP.CodecPackage +org.omg.Messaging +org.omg.PortableInterceptor +org.omg.PortableInterceptor.ORBInitInfoPackage +org.omg.PortableServer +org.omg.PortableServer.CurrentPackage +org.omg.PortableServer.POAManagerPackage +org.omg.PortableServer.POAPackage +org.omg.PortableServer.portable +org.omg.PortableServer.ServantLocatorPackage +org.omg.SendingContext +org.omg.stub.java.rmi +org.w3c.dom +org.w3c.dom.bootstrap +org.w3c.dom.css +org.w3c.dom.events +org.w3c.dom.html +org.w3c.dom.ls +org.w3c.dom.ranges +org.w3c.dom.stylesheets +org.w3c.dom.traversal +org.w3c.dom.views +org.w3c.dom.xpath +org.xml.sax +org.xml.sax.ext +org.xml.sax.helpers diff --git a/ttl-core/src/package-list/spotbugs-annotations/package-list b/ttl-core/src/package-list/spotbugs-annotations/package-list new file mode 100644 index 000000000..9e2f4b649 --- /dev/null +++ b/ttl-core/src/package-list/spotbugs-annotations/package-list @@ -0,0 +1 @@ +edu.umd.cs.findbugs.annotations