ttl-core/src/main/java/com/alibaba/ttl3/TtlCallable.java
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.
* <p>
* Use factory method {@link #get(Callable)} to get decorated instance.
* <p>
* Other TTL Wrapper for common {@code Functional Interface} see {@link TtlWrappers}.
*
* @author Jerry Lee (oldratlee at gmail dot com)
* @see com.alibaba.ttl3.executor.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<V> implements Callable<V>, TtlWrapper<Callable<V>>, TtlEnhanced, TtlAttachments {
private final AtomicReference<Capture> capturedRef;
private final Callable<V> callable;
private final boolean releaseTtlValueReferenceAfterCall;
private TtlCallable(@NonNull Callable<V> 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<V> getCallable() {
return unwrap();
}
/**
* unwrap to the original/underneath {@link Callable}.
*
* @see TtlWrappers#unwrap(Object)
*/
@NonNull
@Override
public Callable<V> unwrap() {
return callable;
}
@Override
public String toString() {
return this.getClass().getName() + " - " + callable.toString();
}
/**
* Factory method, wrap input {@link Callable} to {@link TtlCallable}.
* <p>
* This method is idempotent.
*
* @param callable input {@link Callable}
* @return Wrapped {@link Callable}
*/
@Nullable
@Contract(value = "null -> null; !null -> !null", pure = true)
public static <T> TtlCallable<T> get(@Nullable Callable<T> callable) {
return get(callable, false, false);
}
/**
* Factory method, wrap input {@link Callable} to {@link TtlCallable}.
* <p>
* This method is idempotent.
*
* @param callable input {@link Callable}
* @param releaseTtlValueReferenceAfterCall release TTL value reference after run, avoid memory leak even if {@link TtlRunnable} is referred.
* @return Wrapped {@link Callable}
*/
@Nullable
@Contract(value = "null, _ -> null; !null, _ -> !null", pure = true)
public static <T> TtlCallable<T> get(@Nullable Callable<T> callable, boolean releaseTtlValueReferenceAfterCall) {
return get(callable, releaseTtlValueReferenceAfterCall, false);
}
/**
* Factory method, wrap input {@link Callable} to {@link TtlCallable}.
* <p>
* This method is idempotent.
*
* @param callable input {@link Callable}
* @param releaseTtlValueReferenceAfterCall release TTL value reference after run, avoid memory leak even if {@link TtlRunnable} is referred.
* @param idempotent is idempotent or not. {@code true} will cover up bugs! <b>DO NOT</b> set, only when you know why.
* @return Wrapped {@link Callable}
*/
@Nullable
@Contract(value = "null, _, _ -> null; !null, _, _ -> !null", pure = true)
public static <T> TtlCallable<T> get(@Nullable Callable<T> callable, boolean releaseTtlValueReferenceAfterCall, boolean idempotent) {
if (callable == null) return null;
if (callable instanceof TtlEnhanced) {
// avoid redundant decoration, and ensure idempotency
if (idempotent) return (TtlCallable<T>) callable;
else throw new IllegalStateException("Already TtlCallable!");
}
return new TtlCallable<>(callable, releaseTtlValueReferenceAfterCall);
}
/**
* wrap input {@link Callable} Collection to {@link TtlCallable} Collection.
*
* @param tasks task to be wrapped
* @return Wrapped {@link Callable}
*/
@NonNull
public static <T> List<TtlCallable<T>> gets(@Nullable Collection<? extends Callable<T>> tasks) {
return gets(tasks, false, false);
}
/**
* wrap input {@link Callable} Collection to {@link TtlCallable} Collection.
*
* @param tasks task to be wrapped
* @param releaseTtlValueReferenceAfterCall release TTL value reference after run, avoid memory leak even if {@link TtlRunnable} is referred.
* @return Wrapped {@link Callable}
*/
@NonNull
public static <T> List<TtlCallable<T>> gets(@Nullable Collection<? extends Callable<T>> tasks, boolean releaseTtlValueReferenceAfterCall) {
return gets(tasks, releaseTtlValueReferenceAfterCall, false);
}
/**
* wrap input {@link Callable} Collection to {@link TtlCallable} Collection.
*
* @param tasks task to be wrapped
* @param releaseTtlValueReferenceAfterCall release TTL value reference after run, avoid memory leak even if {@link TtlRunnable} is referred.
* @param idempotent is idempotent or not. {@code true} will cover up bugs! <b>DO NOT</b> set, only when you know why.
* @return Wrapped {@link Callable}
*/
@NonNull
public static <T> List<TtlCallable<T>> gets(@Nullable Collection<? extends Callable<T>> tasks, boolean releaseTtlValueReferenceAfterCall, boolean idempotent) {
if (tasks == null) return Collections.emptyList();
List<TtlCallable<T>> copy = new ArrayList<>();
for (Callable<T> task : tasks) {
copy.add(TtlCallable.get(task, releaseTtlValueReferenceAfterCall, idempotent));
}
return copy;
}
/**
* Unwrap {@link TtlCallable} to the original/underneath one.
* <p>
* this method is {@code null}-safe, when input {@code Callable} parameter is {@code null}, return {@code null};
* if input {@code Callable} parameter is not a {@link TtlCallable} just return input {@code Callable}.
* <p>
* so {@code TtlCallable.unwrap(TtlCallable.get(callable))} will always return the same input {@code callable} object.
*
* @see #get(Callable)
* @see TtlWrappers#unwrap(Object)
*/
@Nullable
@Contract(value = "null -> null; !null -> !null", pure = true)
public static <T> Callable<T> unwrap(@Nullable Callable<T> callable) {
if (!(callable instanceof TtlCallable)) return callable;
else return ((TtlCallable<T>) callable).getCallable();
}
/**
* Unwrap {@link TtlCallable} to the original/underneath one.
* <p>
* Invoke {@link #unwrap(Callable)} for each element in input collection.
* <p>
* This method is {@code null}-safe, when input {@code Callable} collection parameter is {@code null}, return an empty list.
*
* @see #gets(Collection)
* @see #unwrap(Callable)
*/
@NonNull
public static <T> List<Callable<T>> unwraps(@Nullable Collection<? extends Callable<T>> tasks) {
if (tasks == null) return Collections.emptyList();
List<Callable<T>> copy = new ArrayList<>();
for (Callable<T> task : tasks) {
if (!(task instanceof TtlCallable)) copy.add(task);
else copy.add(((TtlCallable<T>) task).getCallable());
}
return copy;
}
private final TtlAttachmentsDelegate ttlAttachment = new TtlAttachmentsDelegate();
/**
* see {@link TtlAttachments#setTtlAttachment(String, Object)}
*/
@Override
public void setTtlAttachment(@NonNull String key, Object value) {
ttlAttachment.setTtlAttachment(key, value);
}
/**
* see {@link TtlAttachments#getTtlAttachment(String)}
*/
@Override
public <T> T getTtlAttachment(@NonNull String key) {
return ttlAttachment.getTtlAttachment(key);
}
}