package freenet.client.async; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; import java.util.Random; import freenet.keys.NodeCHK; import freenet.support.Logger; import freenet.support.io.StorageFormatException; /** Tracks which blocks have been completed, how many attempts have been made for which blocks, * allows choosing a random block, failing a block etc. * @author toad */ public class SimpleBlockChooser { private static volatile boolean logMINOR; private static volatile boolean logDEBUG; static { Logger.registerClass(SimpleBlockChooser.class); } private final int blocks; private final boolean[] completed; private int completedCount; private final int[] retries; protected final int maxRetries; private final Random random; public SimpleBlockChooser(int blocks, Random random, int maxRetries) { this.maxRetries = maxRetries; this.blocks = blocks; this.random = random; this.completed = new boolean[blocks]; this.retries = new int[blocks]; } /** Choose a key to fetch, taking into account retries */ public synchronized int chooseKey() { int max = getMaxBlockNumber(); int[] candidates = new int[max]; int count = 0; int minRetryCount = Integer.MAX_VALUE; for(int i=0;i<max;i++) { int retry = retries[i]; if(retry > maxRetries && maxRetries != -1) continue; if(retry > minRetryCount) continue; if(!checkValid(i)) continue; if(retry < minRetryCount) { count = 0; candidates[count++] = i; minRetryCount = retry; } else if(retry == minRetryCount) { candidates[count++] = i; } // else continue; } if(count == 0) { return -1; } else { return candidates[random.nextInt(count)]; } } public boolean onNonFatalFailure(int blockNo) { return isFatalRetries(innerOnNonFatalFailure(blockNo)); } private boolean isFatalRetries(int retries) { if(maxRetries == -1) return false; return retries > maxRetries; } /** Notify when a block has failed. * @return The total number of attempts for the block so far. Some callers (e.g. inserter) may * fail after a single terminal failure, others after some number of failures (e.g. getter), so * we leave this to the caller. */ protected synchronized int innerOnNonFatalFailure(int blockNo) { return ++retries[blockNo]; } /** Notify when a block has succeeded. */ public boolean onSuccess(int blockNo) { synchronized(this) { if(completed[blockNo]) return false; completed[blockNo] = true; completedCount++; if(completedCount < blocks) { if(logMINOR) Logger.minor(this, "Completed blocks: "+completedCount+"/"+blocks); return true; } } onCompletedAll(); return true; } /** Notify that a block has no longer succeeded. E.g. we downloaded it but now the data is no * longer available due to disk corruption. * @param blockNo */ public synchronized void onUnSuccess(int blockNo) { if(!completed[blockNo]) return; completed[blockNo] = false; completedCount--; } protected void onCompletedAll() { // Do nothing. } /** Is the proposed block valid? Override to implement custom logic e.g. checking which * requests are already running. */ protected boolean checkValid(int chosen) { return !completed[chosen]; } /** Can be overridden to restrict chooseKey() to a subset of the available blocks. Useful for * inserts where we will be able to insert all the blocks until after encoding has finished. * @return The upper bound on the block number chosen. */ protected int getMaxBlockNumber() { return blocks; } /** Mass replace of success/failure. Used by fetchers when we try to decode and fail, possibly * because of disk corruption. * @param used An array of flags indicating whether we have each block. * @return The number of blocks in used. */ public synchronized void replaceSuccesses(boolean[] used) { for(int i=0;i<blocks;i++) { if(used[i] && !completed[i]) onSuccess(i); else if(!used[i] && completed[i]) onUnSuccess(i); } } public synchronized int successCount() { return completedCount; } public synchronized int getRetries(int blockNumber) { return retries[blockNumber]; } /** Ugly to include this here, but avoids making completed visible ... */ public synchronized int getBlockNumber(SplitFileSegmentKeys keys, NodeCHK key) { return keys.getBlockNumber(key, completed); } public synchronized boolean hasSucceeded(int blockNumber) { return completed[blockNumber]; } /** Write the retry counts only, and only if maxRetries != -1. Used if the caller will manage * persistence for the actual list of blocks fetched, as in SplitFileFetcherSegment. * @throws IOException */ public void writeRetries(DataOutputStream dos) throws IOException { if(maxRetries == -1) return; for(int retry : retries) dos.writeInt(retry); } public void readRetries(DataInputStream dis) throws IOException { if(maxRetries == -1) return; for(int i=0;i<blocks;i++) retries[i] = dis.readInt(); } static final int VERSION = 1; /** Write everything * @throws IOException */ public void write(DataOutputStream dos) throws IOException { dos.writeInt(VERSION); for(boolean b : completed) dos.writeBoolean(b); dos.writeInt(maxRetries); writeRetries(dos); } public void read(DataInputStream dis) throws StorageFormatException, IOException { if(dis.readInt() != VERSION) throw new StorageFormatException("Bad version in block chooser"); for(int i=0;i<completed.length;i++) { completed[i] = dis.readBoolean(); if(completed[i]) completedCount++; } if(dis.readInt() != maxRetries) throw new StorageFormatException("Max retries has changed"); readRetries(dis); } public synchronized int countFailedBlocks() { if(maxRetries == -1) return 0; int total = 0; for(int i=0;i<retries.length;i++) { if(completed[i]) continue; if(retries[i] > maxRetries) total++; } return total; } public synchronized boolean[] copyDownloadedBlocks() { return completed.clone(); } public synchronized int countFetchable() { int x = 0; for(int i=0;i<blocks;i++) { if(retries[x] >= maxRetries) continue; if(!checkValid(x)) continue; if(!completed[x]) x++; } return x; } public synchronized boolean hasSucceededAll() { return completedCount == blocks; } }