package freenet.client.async; import java.util.Random; /** A block chooser including support for cooldown */ public class CooldownBlockChooser extends SimpleBlockChooser { public CooldownBlockChooser(int blocks, Random random, int maxRetries, int cooldownTries, long cooldownTime) { super(blocks, random, maxRetries); this.cooldownTries = cooldownTries; this.cooldownTime = cooldownTime; blockCooldownTimes = new long[blocks]; } /** Every cooldownTries attempts, a key will enter cooldown, and won't be re-tried for a period. */ private final int cooldownTries; /** Cooldown lasts this long for each key. */ private final long cooldownTime; /** Time at which the whole block chooser will next become fetchable. 0 to mean it is fetchable * now. Equal to the earliest valid cooldown time for any individual block. INVARIANT: This can * safely be too early (small) but not too late (large). */ private long overallCooldownTime; /** Time at which each block becomes fetchable again. 0 means it is fetchable now. */ private long[] blockCooldownTimes; /** Current time, updated at the beginning of chooseKey(). */ private long now; @Override public synchronized int chooseKey() { now = System.currentTimeMillis(); if(overallCooldownTime > now) return -1; overallCooldownTime = Long.MAX_VALUE; // Will find the earliest wake-up. int ret = super.chooseKey(); if(ret != -1) overallCooldownTime = 0; // Fetchable now, else waiting for cooldown. return ret; } @Override protected boolean checkValid(int blockNo) { if(!super.checkValid(blockNo)) return false; long wakeUp = blockCooldownTimes[blockNo]; if(now > wakeUp) { blockCooldownTimes[blockNo] = 0; return true; } else { // Update the overall cooldown wakeup time. overallCooldownTime = Math.min(overallCooldownTime, wakeUp); return false; } } @Override protected synchronized int innerOnNonFatalFailure(int blockNo) { int ret = super.innerOnNonFatalFailure(blockNo); if(ret > maxRetries && maxRetries != -1) return ret; if(ret % cooldownTries == 0) { blockCooldownTimes[blockNo] = System.currentTimeMillis() + cooldownTime; overallCooldownTime = Math.min(blockCooldownTimes[blockNo], overallCooldownTime); // Must not be left at infinite! } else { // Fetchable. blockCooldownTimes[blockNo] = 0; overallCooldownTime = 0; } return ret; } /** Should be called e.g. when getMaxBlockNumber() changes. */ public final synchronized void clearCooldown() { overallCooldownTime = 0; } @Override public synchronized void onUnSuccess(int blockNo) { blockCooldownTimes[blockNo] = 0; clearCooldown(); } public synchronized long overallCooldownTime() { return overallCooldownTime; } public synchronized long getCooldownTime(int blockNumber) { if(hasSucceeded(blockNumber)) return 0; return blockCooldownTimes[blockNumber]; } }