package io.datakernel.cube.attributes;
import io.datakernel.annotation.Nullable;
import io.datakernel.async.CompletionCallback;
import io.datakernel.async.ForwardingResultCallback;
import io.datakernel.async.ResultCallback;
import io.datakernel.eventloop.Eventloop;
import io.datakernel.eventloop.EventloopService;
import io.datakernel.eventloop.ScheduledRunnable;
import io.datakernel.jmx.JmxAttribute;
import io.datakernel.jmx.JmxOperation;
import io.datakernel.jmx.ValueStats;
import java.util.HashMap;
import java.util.Map;
import static io.datakernel.jmx.ValueStats.SMOOTHING_WINDOW_1_HOUR;
public abstract class ReloadingAttributeResolver<K, A> extends AbstractAttributeResolver<K, A> implements EventloopService {
protected final Eventloop eventloop;
private long timestamp;
private long reloadPeriod;
private long retryPeriod = 1000L;
private ScheduledRunnable scheduledRunnable;
private final Map<K, A> cache = new HashMap<>();
private int reloads;
private int reloadErrors;
private int resolveErrors;
private K lastResolveErrorKey;
private final ValueStats reloadTime = ValueStats.create(SMOOTHING_WINDOW_1_HOUR);
protected ReloadingAttributeResolver(Eventloop eventloop) {
this.eventloop = eventloop;
}
@Override
protected final A resolveAttributes(K key) {
A result = cache.get(key);
if (result == null) {
resolveErrors++;
lastResolveErrorKey = key;
}
return result;
}
protected abstract void reload(@Nullable long lastTimestamp, ResultCallback<Map<K, A>> callback);
private void doReload() {
reloads++;
scheduledRunnable.cancel();
final long reloadTimestamp = getEventloop().currentTimeMillis();
reload(timestamp, new ResultCallback<Map<K, A>>() {
@Override
protected void onResult(Map<K, A> result) {
reloadTime.recordValue((int) (getEventloop().currentTimeMillis() - reloadTimestamp));
cache.putAll(result);
timestamp = reloadTimestamp;
scheduleReload(reloadPeriod);
}
@Override
protected void onException(Exception e) {
reloadErrors++;
scheduleReload(retryPeriod);
}
});
}
private void scheduleReload(long period) {
Eventloop eventloop = getEventloop();
scheduledRunnable = eventloop.schedule(eventloop.currentTimeMillis() + period, new Runnable() {
@Override
public void run() {
doReload();
}
});
}
@Override
public Eventloop getEventloop() {
return eventloop;
}
@Override
public void start(final CompletionCallback callback) {
final long reloadTimestamp = getEventloop().currentTimeMillis();
reload(timestamp, new ForwardingResultCallback<Map<K, A>>(callback) {
@Override
protected void onResult(Map<K, A> result) {
reloadTime.recordValue((int) (getEventloop().currentTimeMillis() - reloadTimestamp));
cache.putAll(result);
timestamp = reloadTimestamp;
scheduleReload(reloadPeriod);
callback.setComplete();
}
});
}
@Override
public void stop(CompletionCallback callback) {
scheduledRunnable.cancel();
callback.setComplete();
}
@JmxOperation
public void reload() {
doReload();
}
@JmxAttribute
public long getReloadPeriod() {
return reloadPeriod;
}
@JmxAttribute
public void setReloadPeriod(long reloadPeriod) {
this.reloadPeriod = reloadPeriod;
}
@JmxAttribute
public long getRetryPeriod() {
return retryPeriod;
}
@JmxAttribute
public void setRetryPeriod(long retryPeriod) {
this.retryPeriod = retryPeriod;
}
@JmxAttribute
public int getReloads() {
return reloads;
}
@JmxAttribute
public int getReloadErrors() {
return reloadErrors;
}
@JmxAttribute
public int getResolveErrors() {
return resolveErrors;
}
@JmxAttribute
public K getLastResolveErrorKey() {
return lastResolveErrorKey;
}
@JmxAttribute
public ValueStats getReloadTime() {
return reloadTime;
}
}