package lbms.plugins.mldht.kad;
import java.net.Inet6Address;
import java.net.InetSocketAddress;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
public class NonReachableCache {
final static long PURGE_TIME_MULTIPLIER = TimeUnit.MINUTES.toMillis(5);
// v6 addresses are unlikely to suffer from dynamic IP reuse, it should be safe to cache them longer
final static long PURGE_TIME_MULTIPLIER_V6 = TimeUnit.MINUTES.toMillis(15);
static class CacheEntry {
long created;
int failures;
}
ConcurrentHashMap<InetSocketAddress, CacheEntry> map = new ConcurrentHashMap<>();
void onCallFinished(RPCCall c) {
InetSocketAddress addr = c.getRequest().getDestination();
RPCState state = c.state();
if(state != RPCState.TIMEOUT && state != RPCState.RESPONDED)
return;
map.compute(addr, (k, oldEntry) -> {
if(oldEntry != null) {
CacheEntry updatedEntry = new CacheEntry();
updatedEntry.created = oldEntry.created;
// AIMD
if(state == RPCState.TIMEOUT)
updatedEntry.failures = oldEntry.failures + 1;
else
updatedEntry.failures /= 2;
if(updatedEntry.failures == 0)
return null;
return updatedEntry;
}
CacheEntry newEntry = new CacheEntry();
newEntry.created = System.currentTimeMillis();
newEntry.failures = 1;
return newEntry;
});
}
public int getFailures(InetSocketAddress addr) {
return Optional.ofNullable(map.get(addr)).map(e -> e.failures).orElse(0);
}
void cleanStaleEntries() {
long now = System.currentTimeMillis();
map.entrySet().removeIf(e -> {
CacheEntry v = e.getValue();
long multiplier = e.getKey().getAddress() instanceof Inet6Address ? PURGE_TIME_MULTIPLIER_V6 : PURGE_TIME_MULTIPLIER;
return now - v.created > v.failures * multiplier;
});
}
}