package freenet.support; import com.db4o.ObjectContainer; import freenet.client.async.ClientContext; /** * An array which supports very fast remove-and-return-a-random-element. */ // WARNING: THIS CLASS IS STORED IN DB4O -- THINK TWICE BEFORE ADD/REMOVE/RENAME FIELDS/ public class RandomGrabArray implements RemoveRandom { private static volatile boolean logMINOR; static { Logger.registerLogThresholdCallback(new LogThresholdCallback() { @Override public void shouldUpdate() { logMINOR = Logger.shouldLog(Logger.MINOR, this); } }); } private static class Block { // WARNING: THIS CLASS IS STORED IN DB4O -- THINK TWICE BEFORE ADD/REMOVE/RENAME FIELDS/ RandomGrabArrayItem[] reqs; } /** Array of items. Non-null's followed by null's. * We used to have a Set so we could check whether something is in the set quickly. * We got rid of this because for persistent requests it is vastly faster to just loop the * loop and check ==, and for non-persistent requests it doesn't matter much. */ private Block[] blocks; /** Index of first null item. */ private int index; private final static int MIN_SIZE = 32; private final static int BLOCK_SIZE = 1024; private final boolean persistent; private final int hashCode; private final RemoveRandomParent parent; public RandomGrabArray(boolean persistent, ObjectContainer container, RemoveRandomParent parent) { this.blocks = new Block[] { new Block() }; blocks[0].reqs = new RandomGrabArrayItem[MIN_SIZE]; this.persistent = persistent; index = 0; this.hashCode = super.hashCode(); this.parent = parent; } @Override public int hashCode() { return hashCode; } public void add(RandomGrabArrayItem req, ObjectContainer container) { if(req.persistent() != persistent) throw new IllegalArgumentException("req.persistent()="+req.persistent()+" but array.persistent="+persistent+" item="+req+" array="+this); if(req.isEmpty(container)) { if(logMINOR) Logger.minor(this, "Is finished already: "+req); return; } req.setParentGrabArray(this, container); synchronized(this) { int x = 0; if(blocks.length == 1 && index < BLOCK_SIZE) { if(persistent) container.activate(blocks[0], 1); for(int i=0;i= blocks[0].reqs.length) { int newSize = Math.min(BLOCK_SIZE, blocks[0].reqs.length*2); RandomGrabArrayItem[] newReqs = new RandomGrabArrayItem[newSize]; System.arraycopy(blocks[0].reqs, 0, newReqs, 0, blocks[0].reqs.length); blocks[0].reqs = newReqs; } blocks[0].reqs[index++] = req; if(persistent) { container.store(blocks[0]); container.store(this); container.deactivate(blocks[0], 1); } return; } int targetBlock = index / BLOCK_SIZE; for(int i=0;i= index) break; if(block.reqs[j] == req) { if(logMINOR) Logger.minor(this, "Already contains "+req+" : "+this+" size now "+index); if(persistent) container.deactivate(block, 1); return; } if(block.reqs[j] == null) { Logger.error(this, "reqs["+i+"."+j+"] = null on "+this); } x++; } if(persistent && i != targetBlock) container.deactivate(block, 1); } int oldBlockLen = blocks.length; if(blocks.length <= targetBlock) { if(logMINOR) Logger.minor(this, "Adding blocks on "+this); Block[] newBlocks = new Block[targetBlock + 1]; System.arraycopy(blocks, 0, newBlocks, 0, blocks.length); for(int i=blocks.length;i MAX_EXCLUDED) { Logger.normal(this, "Remove random returning null because "+excluded+" excluded items, length = "+index, new Exception("error")); if(persistent && changedMe) container.store(this); return null; } continue; } if(ret != null) { if(logMINOR) Logger.minor(this, "Returning (cannot remove): "+ret+" of "+index); if(persistent && changedMe) container.store(this); return ret; } // Remove an element. do { changedMe = true; remove(blockNo, i, container); if(persistent && oret != null && ret == null) // if ret != null we will return it container.deactivate(oret, 1); oret = blocks[blockNo].reqs[i % BLOCK_SIZE]; // Check for nulls, but don't check for cancelled, since we'd have to activate. } while (index > i && oret == null); // Shrink array if(blocks.length == 1 && index < blocks[0].reqs.length / 4) { changedMe = true; // Shrink array int newSize = Math.max(index * 2, MIN_SIZE); RandomGrabArrayItem[] r = new RandomGrabArrayItem[newSize]; System.arraycopy(blocks[0].reqs, 0, r, 0, r.length); blocks[0].reqs = r; if(persistent) container.store(this); } else if(blocks.length > 1 && (((index + (BLOCK_SIZE/2)) / BLOCK_SIZE) + 1) < blocks.length) { if(logMINOR) Logger.minor(this, "Shrinking blocks on "+this); Block[] newBlocks = new Block[((index + (BLOCK_SIZE/2)) / BLOCK_SIZE) + 1]; System.arraycopy(blocks, 0, newBlocks, 0, newBlocks.length); if(persistent) { container.store(this); for(int x=newBlocks.length;x= index) break; x++; if(block.reqs[i] == it) { int pullFrom = --index; int idx = pullFrom % BLOCK_SIZE; int endBlock = pullFrom / BLOCK_SIZE; if(i == endBlock) { block.reqs[j] = block.reqs[idx]; block.reqs[idx] = null; } else { Block fromBlock = blocks[endBlock]; if(persistent) container.activate(fromBlock, 1); block.reqs[j] = fromBlock.reqs[idx]; fromBlock.reqs[idx] = null; if(persistent) { container.store(fromBlock); container.deactivate(fromBlock, 1); } } if(persistent) container.store(block); matched = true; break; } } if(persistent) container.deactivate(block, 1); } if(index == 0) empty = true; } } if(it.getParentGrabArray() == this) it.setParentGrabArray(null, container); else Logger.error(this, "Removing item "+it+" from "+this+" but RGA is "+it.getParentGrabArray(), new Exception("debug")); if(!matched) return; if(persistent) { container.store(this); } if(empty && parent != null) { boolean active = true; if(persistent) active = container.ext().isActive(parent); if(!active) container.activate(parent, 1); parent.maybeRemove(this, container); if(!active) container.deactivate(parent, 1); } } public synchronized boolean isEmpty() { return index == 0; } public boolean persistent() { return persistent; } public boolean contains(RandomGrabArrayItem item, ObjectContainer container) { synchronized(this) { if(blocks.length == 1) { Block block = blocks[0]; if(persistent) container.activate(block, 1); for(int i=0;i= index) break; x++; if(block.reqs[i] == item) { if(persistent) container.deactivate(block, 1); return true; } } if(persistent) container.deactivate(block, 1); } } } return false; } public synchronized int size() { return index; } public synchronized RandomGrabArrayItem get(int idx, ObjectContainer container) { int blockNo = idx / BLOCK_SIZE; if(persistent) container.activate(blocks[blockNo], 1); RandomGrabArrayItem item = blocks[blockNo].reqs[idx % BLOCK_SIZE]; if(persistent) container.deactivate(blocks[blockNo], 1); return item; } public void removeFrom(ObjectContainer container) { if(blocks != null) { for(Block block : blocks) { container.activate(block, 1); for(RandomGrabArrayItem item : block.reqs) { if(item != null) { Logger.error(this, "VALID ITEM WHILE DELETING BLOCK: "+item+" on "+this); } } container.delete(block); } } container.delete(this); } }