package com.alibaba.ttl; import com.alibaba.ttl.spi.TtlAttachments; import com.alibaba.ttl.spi.TtlAttachmentsDelegate; import com.alibaba.ttl.spi.TtlEnhanced; import com.alibaba.ttl.spi.TtlWrapper; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.concurrent.atomic.AtomicReference; import static com.alibaba.ttl.TransmittableThreadLocal.Transmitter.*; /** * {@link TtlRunnable} decorate {@link Runnable}, so as to get {@link TransmittableThreadLocal} * and transmit it to the time of {@link Runnable} execution, needed when use {@link Runnable} to thread pool. *

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

* Other TTL Wrapper for common {@code Functional Interface} see {@link TtlWrappers}. * * @author Jerry Lee (oldratlee at gmail dot com) * @see com.alibaba.ttl.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 * @since 0.9.0 */ public final class TtlRunnable implements Runnable, TtlWrapper, TtlEnhanced, TtlAttachments { private final AtomicReference capturedRef; private final Runnable runnable; private final boolean releaseTtlValueReferenceAfterRun; private TtlRunnable(@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 Object captured = capturedRef.get(); if (captured == null || releaseTtlValueReferenceAfterRun && !capturedRef.compareAndSet(captured, null)) { throw new IllegalStateException("TTL value reference is released after run!"); } final Object 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 TtlUnwrap#unwrap(Object) * @since 2.11.4 */ @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 public static TtlRunnable get(@Nullable Runnable runnable) { return get(runnable, false, false); } /** * Factory method, wrap input {@link Runnable} to {@link TtlRunnable}. * * @param runnable input {@link Runnable}. if input is {@code null}, return {@code null}. * @param releaseTtlValueReferenceAfterRun release TTL value reference after run, avoid memory leak even if {@link TtlRunnable} is referred. * @return Wrapped {@link Runnable} * @throws IllegalStateException when input is {@link TtlRunnable} already. */ @Nullable public static TtlRunnable get(@Nullable Runnable runnable, boolean releaseTtlValueReferenceAfterRun) { return get(runnable, releaseTtlValueReferenceAfterRun, false); } /** * Factory method, wrap input {@link Runnable} to {@link TtlRunnable}. * * @param runnable input {@link Runnable}. if input is {@code null}, return {@code null}. * @param releaseTtlValueReferenceAfterRun release TTL value reference after run, avoid memory leak even if {@link TtlRunnable} is referred. * @param idempotent is idempotent mode or not. if {@code true}, just return input {@link Runnable} when it's {@link TtlRunnable}, * otherwise throw {@link IllegalStateException}. * Caution: {@code true} will cover up bugs! DO NOT set, only when you know why. * @return Wrapped {@link Runnable} * @throws IllegalStateException when input is {@link TtlRunnable} already and not idempotent. */ @Nullable public static TtlRunnable get(@Nullable Runnable runnable, boolean releaseTtlValueReferenceAfterRun, boolean idempotent) { if (null == runnable) return null; if (runnable instanceof TtlEnhanced) { // avoid redundant decoration, and ensure idempotency if (idempotent) return (TtlRunnable) runnable; else throw new IllegalStateException("Already TtlRunnable!"); } return new TtlRunnable(runnable, releaseTtlValueReferenceAfterRun); } /** * wrap input {@link Runnable} Collection to {@link TtlRunnable} Collection. * * @param tasks task to be wrapped. if input is {@code null}, return {@code null}. * @return wrapped tasks * @throws IllegalStateException when input is {@link TtlRunnable} already. */ @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 com.alibaba.ttl.TtlUnwrap#unwrap(Object) * @since 2.10.2 */ @Nullable public static Runnable unwrap(@Nullable Runnable runnable) { if (!(runnable instanceof TtlRunnable)) return runnable; else return ((TtlRunnable) runnable).getRunnable(); } /** * Unwrap {@link TtlRunnable} to the original/underneath one for collection. *

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

* This method is {@code null}-safe, when input {@code Runnable} parameter collection is {@code null}, return a empty list. * * @see #gets(Collection) * @see #unwrap(Runnable) * @since 2.10.2 */ @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)} * * @since 2.11.0 */ @Override public void setTtlAttachment(@NonNull String key, Object value) { ttlAttachment.setTtlAttachment(key, value); } /** * see {@link TtlAttachments#getTtlAttachment(String)} * * @since 2.11.0 */ @Override public T getTtlAttachment(@NonNull String key) { return ttlAttachment.getTtlAttachment(key); } }