package io.cattle.platform.engine.idempotent;
import io.cattle.platform.archaius.util.ArchaiusUtil;
import io.cattle.platform.util.exception.ExceptionUtils;
import java.util.HashSet;
import java.util.Set;
import org.apache.cloudstack.managed.threadlocal.ManagedThreadLocal;
import org.apache.commons.lang3.ObjectUtils;
import com.netflix.config.DynamicBooleanProperty;
import com.netflix.config.DynamicIntProperty;
public class Idempotent {
private static final int LOOP_MAX = 1000;
private static final DynamicBooleanProperty CHECKS = ArchaiusUtil.getBoolean("idempotent.checks");
private static final DynamicIntProperty LOOP_COUNT = ArchaiusUtil.getInt("idempotent.retry.count");
private static final ThreadLocal<Set<String>> STACK_TRACES = new ManagedThreadLocal<Set<String>>() {
@Override
protected Set<String> initialValue() {
return new HashSet<>();
}
};
private static final ThreadLocal<Boolean> DISABLED = new ManagedThreadLocal<Boolean>();
private static final ThreadLocal<Long> LEVEL = new ManagedThreadLocal<Long>() {
@Override
protected Long initialValue() {
return new Long(0);
}
};
public static boolean enabled() {
return CHECKS.get() && ! Boolean.TRUE.equals(DISABLED.get());
}
public static <T> T execute(IdempotentExecution<T> execution) {
if (!Idempotent.enabled()) {
return execution.execute();
}
long level = LEVEL.get();
try {
LEVEL.set(level + 1);
T result = null;
int max = level > 0 ? 1 : LOOP_COUNT.get();
for (int i = 0; i < max; i++) {
for (int j = 0 ; j < LOOP_MAX; j++) {
try {
T resultAgain = execution.execute();
if (i == 0) {
result = resultAgain;
}
if (Boolean.TRUE.equals(DISABLED.get())) {
return resultAgain;
}
if (!ObjectUtils.equals(result, resultAgain)) {
throw new OperationNotIdemponent("Result [" + result + "] does not match second result [" + resultAgain + "]");
}
i+=j;
break;
} catch (IdempotentRetryException e) {
if (j == LOOP_MAX - 1) {
throw new IllegalStateException("Executed [" + execution + "] " + LOOP_MAX + " times and never completed");
}
}
}
}
return result;
} finally {
LEVEL.set(level);
}
}
public static void tempDisable() {
DISABLED.set(true);
}
public static <T> T change(IdempotentExecution<T> execution) {
if (!Idempotent.enabled() || LEVEL.get() < 1) {
return execution.execute();
}
Set<String> traces = STACK_TRACES.get();
IdempotentRetryException e = new IdempotentRetryException();
String trace = ExceptionUtils.toString(e);
if (!traces.contains(trace)) {
traces.add(trace);
throw e;
}
return execution.execute();
}
}