package net.windwards.dnsfrontend.frontend;
import net.sf.ehcache.CacheException;
import net.sf.ehcache.Ehcache;
import net.sf.ehcache.Element;
import net.windwards.dnsfrontend.api.AbstractTaskKeeper;
import net.windwards.dnsfrontend.api.Backend;
import net.windwards.dnsfrontend.api.BackendRepository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xbill.DNS.DClass;
import org.xbill.DNS.Name;
import org.xbill.DNS.Record;
import org.xbill.DNS.TextParseException;
import org.xbill.DNS.Type;
import org.xbill.DNS.UNKRecord;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class TaskKeeperImpl extends AbstractTaskKeeper {
private Logger logger = LoggerFactory.getLogger(TaskKeeperImpl.class);
protected ScheduledThreadPoolExecutor pool;
protected Ehcache cache;
protected Map<String, FuturePackage> futures;
protected static class FuturePackage {
public ResolveTask task;
public ScheduledFuture future;
public FuturePackage(ResolveTask task, ScheduledFuture future) {
this.task = task;
this.future = future;
}
}
public static final Record Unknown = null;
static {
try {
Record.newRecord(new Name("."), Type.ANY, DClass.IN);
} catch (TextParseException ignore) {
}
}
public TaskKeeperImpl(Ehcache cache) {
this.futures = new ConcurrentHashMap<String, FuturePackage>(100);
this.cache = cache;
}
@Override
public void start() {
this.pool = new ScheduledThreadPoolExecutor(10);
this.cache.getCacheEventNotificationService().registerListener(this);
}
@Override
public void stop() {
this.pool.shutdown();
}
@Override
public void keep(final ResolveTask task) {
Runnable timeout = new Runnable() {
@Override
public void run() {
Element lmnt = new Element(task.getIdent(), Unknown);
lmnt.setTimeToLive(negativeExpiry);
cache.put(lmnt);
}
};
ScheduledFuture future = this.pool.schedule(
timeout, this.expireTime, TimeUnit.MILLISECONDS);
String ident = task.getIdent();
this.futures.put(ident, new FuturePackage(task, future));
}
@Override
public void replyArrived(Ehcache cache, Element lmnt) throws CacheException {
logger.debug("Cache update received [lmnt={}]", lmnt);
Record record = (Record) lmnt.getObjectValue();
FuturePackage pack;
if(record == Unknown) {
pack = this.futures.remove(lmnt.getObjectKey());
pack.task.timeout();
return;
}
Backend backend = this.repo.lookup(record.getType());
String key = backend.makeKey(record);
pack = this.futures.remove(key);
if (pack == null) {
logger.info("Unsolicited cache insert [key={}, record={}]", key, record);
return;
}
if (pack.future.cancel(false)) {
pack.task.answer(record, false);
} else {
// Obscure race
logger.warn("Race with timeout [name={}]", pack.task.getIdent());
}
}
@Override
public void discard(ResolveTask task) {
this.futures.remove(task.getIdent());
}
}