package com.alibaba.ttl;
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.
*
* @author Jerry Lee (oldratlee at gmail dot com)
* @see com.alibaba.ttl.threadpool.TtlExecutors
* @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 {
private final AtomicReference capturedRef;
private final Runnable runnable;
private final boolean releaseTtlValueReferenceAfterRun;
private TtlRunnable(Runnable runnable, boolean releaseTtlValueReferenceAfterRun) {
this.capturedRef = new AtomicReference(capture());
this.runnable = runnable;
this.releaseTtlValueReferenceAfterRun = releaseTtlValueReferenceAfterRun;
}
/**
* wrap method {@link Runnable#run()}.
*/
@Override
public void run() {
Object captured = capturedRef.get();
if (captured == null || releaseTtlValueReferenceAfterRun && !capturedRef.compareAndSet(captured, null)) {
throw new IllegalStateException("TTL value reference is released after run!");
}
Object backup = replay(captured);
try {
runnable.run();
} finally {
restore(backup);
}
}
/**
* return original/unwrapped {@link Runnable}.
*/
public Runnable getRunnable() {
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, wrapper 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.
*/
public static TtlRunnable get(Runnable runnable) {
return get(runnable, false, false);
}
/**
* Factory method, wrapper 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.
*/
public static TtlRunnable get(Runnable runnable, boolean releaseTtlValueReferenceAfterRun) {
return get(runnable, releaseTtlValueReferenceAfterRun, false);
}
/**
* Factory method, wrapper 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.
*/
public static TtlRunnable get(Runnable runnable, boolean releaseTtlValueReferenceAfterRun, boolean idempotent) {
if (null == runnable) {
return null;
}
if (runnable instanceof TtlRunnable) {
if (idempotent) {
// avoid redundant decoration, and ensure idempotency
return (TtlRunnable) runnable;
} else {
throw new IllegalStateException("Already TtlRunnable!");
}
}
return new TtlRunnable(runnable, releaseTtlValueReferenceAfterRun);
}
/**
* wrapper 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.
*/
public static List gets(Collection extends Runnable> tasks) {
return gets(tasks, false, false);
}
/**
* wrapper 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.
*/
public static List gets(Collection extends Runnable> tasks, boolean releaseTtlValueReferenceAfterRun) {
return gets(tasks, releaseTtlValueReferenceAfterRun, false);
}
/**
* wrapper 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.
*/
public static List gets(Collection extends Runnable> 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;
}
}