package freenet.support;
import java.util.Arrays;
import freenet.client.async.ClientContext;
import freenet.client.async.ClientRequestSelector;
import freenet.client.async.RequestSelectionTreeNode;
/**
* Like RandomGrabArray, but there is an equal chance of any given client's requests being
* returned. Again, not persistent; this is reconstructed on restart.
*
* LOCKING: There is a single lock for the entire tree, the ClientRequestSelector. This must be
* taken before calling any methods on RGA or SRGA. See the javadocs there for deeper explanation.
*
* A lot of this is over-complicated and over-expensive because of db4o. A lot of it is O(n).
* This is all kept in RAM now so we can change it at will, plus there is only one object
* queued per splitfile now, so memory pressure is much less of an issue.
* FIXME Simplify and improve performance!
*/
public class SectoredRandomGrabArray<T, C extends RemoveRandomWithObject<T>> implements RemoveRandom, RemoveRandomParent, RequestSelectionTreeNode {
private static volatile boolean logMINOR;
static {
Logger.registerClass(SectoredRandomGrabArray.class);
}
protected RemoveRandomWithObject<T>[] grabArrays;
private T[] grabClients;
private RemoveRandomParent parent;
protected final ClientRequestSelector root;
private long wakeupTime;
public SectoredRandomGrabArray(RemoveRandomParent parent, ClientRequestSelector root) {
grabClients = (T[]) new Object[0];
grabArrays = new RemoveRandomWithObject[0];
this.parent = parent;
this.root = root;
}
protected void addElement(T client, C rga) {
synchronized(root) {
final int len = grabArrays.length;
grabArrays = Arrays.copyOf(grabArrays, len+1);
grabArrays[len] = rga;
grabClients = Arrays.copyOf(grabClients, len+1);
grabClients[len] = client;
}
}
protected int haveClient(T client) {
synchronized(root) {
for(int i=0;i<grabClients.length;i++) {
if(grabClients[i] == client) return i;
}
return -1;
}
}
/**
* Get a grabber.
*/
public C getGrabber(T client) {
synchronized(root) {
int idx = haveClient(client);
if(idx == -1) return null;
else return (C) grabArrays[idx];
}
}
public T getClient(int x) {
synchronized(root) {
return grabClients[x];
}
}
/**
* Put a grabber.
*/
public void addGrabber(T client, C requestGrabber, ClientContext context) {
synchronized(root) {
if(requestGrabber.getObject() != client)
throw new IllegalArgumentException("Client not equal to RemoveRandomWithObject's client: client="+client+" rr="+requestGrabber+" his object="+requestGrabber.getObject());
addElement(client, requestGrabber);
if(context != null) {
clearWakeupTime(context);
}
}
}
@Override
public RemoveRandomReturn removeRandom(RandomGrabArrayItemExclusionList excluding, ClientContext context, long now) {
synchronized(root) {
while(true) {
if(grabArrays.length == 0) return null;
if(grabArrays.length == 1) {
return removeRandomOneOnly(excluding, context, now);
}
if(grabArrays.length == 2) {
RemoveRandomReturn ret = removeRandomTwoOnly(excluding, context, now);
if(ret == null) continue; // Go around loop again, it has reduced to 1 or 0.
return ret;
}
RandomGrabArrayItem item = removeRandomLimited(excluding, context, now);
if(item != null)
return new RemoveRandomReturn(item);
else
return removeRandomExhaustive(excluding, context, now);
}
}
}
private RemoveRandomReturn removeRandomExhaustive(
RandomGrabArrayItemExclusionList excluding,
ClientContext context, long now) {
synchronized(root) {
long wakeupTime = Long.MAX_VALUE;
if(grabArrays.length == 0) return null;
int x = context.fastWeakRandom.nextInt(grabArrays.length);
for(int i=0;i<grabArrays.length;i++) {
x++;
if(x >= grabArrays.length) x = 0;
RemoveRandomWithObject<T> rga = grabArrays[x];
long excludeTime = rga.getWakeupTime(context, now);
if(excludeTime > 0) {
if(wakeupTime > excludeTime) wakeupTime = excludeTime;
continue;
}
if(logMINOR)
Logger.minor(this, "Picked "+x+" of "+grabArrays.length+" : "+rga+" on "+this);
RandomGrabArrayItem item = null;
RemoveRandomReturn val = rga.removeRandom(excluding, context, now);
if(val != null) {
if(val.item != null)
item = val.item;
else {
if(wakeupTime > val.wakeupTime) wakeupTime = val.wakeupTime;
}
}
if(logMINOR)
Logger.minor(this, "RGA has picked "+x+"/"+grabArrays.length+": "+item+
" rga.isEmpty="+rga.isEmpty());
if(item != null) {
return new RemoveRandomReturn(item);
} else if(rga.isEmpty()) {
if(logMINOR)
Logger.minor(this, "Removing grab array "+x+" : "+rga+" (is empty)");
removeElement(x);
}
}
reduceWakeupTime(wakeupTime, context);
return new RemoveRandomReturn(wakeupTime);
}
}
private RandomGrabArrayItem removeRandomLimited(
RandomGrabArrayItemExclusionList excluding,
ClientContext context, long now) {
synchronized(root) {
/** Count of arrays that have items but didn't return anything because of exclusions */
final int MAX_EXCLUDED = 10;
int excluded = 0;
while(true) {
if(grabArrays.length == 0) return null;
int x = context.fastWeakRandom.nextInt(grabArrays.length);
RemoveRandomWithObject<T> rga = grabArrays[x];
if(rga == null) {
// We handle this in the other cases so we should handle it here.
Logger.error(this, "Slot "+x+" is null for client "+grabClients[x]);
excluded++;
if(excluded > MAX_EXCLUDED) {
Logger.normal(this, "Too many sub-arrays are entirely excluded on "+this+" length = "+grabArrays.length, new Exception("error"));
return null;
}
continue;
}
long excludeTime = rga.getWakeupTime(context, now);
if(excludeTime > 0) {
excluded++;
if(excluded > MAX_EXCLUDED) {
Logger.normal(this, "Too many sub-arrays are entirely excluded on "+this+" length = "+grabArrays.length, new Exception("error"));
return null;
}
continue;
}
if(logMINOR)
Logger.minor(this, "Picked "+x+" of "+grabArrays.length+" : "+rga+" on "+this);
RandomGrabArrayItem item = null;
RemoveRandomReturn val = rga.removeRandom(excluding, context, now);
if(val != null && val.item != null) item = val.item;
if(logMINOR)
Logger.minor(this, "RGA has picked "+x+"/"+grabArrays.length+": "+item+
" rga.isEmpty="+rga.isEmpty());
// If it is not empty but returns null we exclude it, and count the exclusion.
// If it is empty we remove it, and don't count the exclusion.
if(item != null) {
return item;
} else {
if(rga.isEmpty()) {
if(logMINOR)
Logger.minor(this, "Removing grab array "+x+" : "+rga+" (is empty)");
removeElement(x);
} else {
excluded++;
if(excluded > MAX_EXCLUDED) {
Logger.normal(this, "Too many sub-arrays are entirely excluded on "+this+" length = "+grabArrays.length, new Exception("error"));
return null;
}
}
continue;
}
}
}
}
private RemoveRandomReturn removeRandomTwoOnly(
RandomGrabArrayItemExclusionList excluding,
ClientContext context, long now) {
synchronized(root) {
long wakeupTime = Long.MAX_VALUE;
// Another simple common case
int x = context.fastWeakRandom.nextBoolean() ? 1 : 0;
RemoveRandomWithObject<T> rga = grabArrays[x];
RemoveRandomWithObject<T> firstRGA = rga;
if(rga == null) {
Logger.error(this, "rga = null on "+this);
if(grabArrays[1-x] == null) {
Logger.error(this, "other rga is also null on "+this);
grabArrays = (C[]) new RemoveRandomWithObject[0];
grabClients = (T[]) new Object[0];
return null;
} else {
Logger.error(this, "grabArrays["+(1-x)+"] is valid but ["+x+"] is null, correcting...");
grabArrays = (C[]) new RemoveRandomWithObject[] { grabArrays[1-x] };
grabClients = (T[]) new Object[] { grabClients[1-x] };
return null;
}
}
RandomGrabArrayItem item = null;
RemoveRandomReturn val = null;
if(logMINOR) Logger.minor(this, "Only 2, trying "+rga);
long excludeTime = rga.getWakeupTime(context, now);
if(excludeTime > 0) {
wakeupTime = excludeTime;
rga = null;
firstRGA = null;
} else {
val = rga.removeRandom(excluding, context, now);
if(val != null) {
if(val.item != null)
item = val.item;
else {
if(wakeupTime > val.wakeupTime) wakeupTime = val.wakeupTime;
}
}
}
if(item != null) {
if(logMINOR)
Logger.minor(this, "Returning (two items only) "+item+" for "+rga);
return new RemoveRandomReturn(item);
} else {
x = 1-x;
rga = grabArrays[x];
if(rga == null) {
Logger.error(this, "Other RGA is null later on on "+this);
grabArrays = (C[]) new RemoveRandomWithObject[] { grabArrays[1-x] };
grabClients = (T[]) new Object[] { grabClients[1-x] };
reduceWakeupTime(wakeupTime, context);
return new RemoveRandomReturn(wakeupTime);
}
excludeTime = rga.getWakeupTime(context, now);
if(excludeTime > 0) {
if(wakeupTime > excludeTime) wakeupTime = excludeTime;
rga = null;
} else {
val = rga.removeRandom(excluding, context, now);
if(val != null) {
if(val.item != null)
item = val.item;
else {
if(wakeupTime > val.wakeupTime) wakeupTime = val.wakeupTime;
}
}
}
if(firstRGA != null && firstRGA.isEmpty() && rga != null && rga.isEmpty()) {
if(logMINOR) Logger.minor(this, "Removing both on "+this+" : "+firstRGA+" and "+rga+" are empty");
grabArrays = (C[]) new RemoveRandomWithObject[0];
grabClients = (T[]) new Object[0];
} else if(firstRGA != null && firstRGA.isEmpty()) {
if(logMINOR) Logger.minor(this, "Removing first: "+firstRGA+" is empty on "+this);
grabArrays = (C[]) new RemoveRandomWithObject[] { grabArrays[x] }; // don't use RGA, it may be nulled out
grabClients = (T[]) new Object[] { grabClients[x] };
}
if(logMINOR)
Logger.minor(this, "Returning (two items only) "+item+" for "+rga);
if(item == null) {
if(grabArrays.length == 0)
return null; // Remove this as well
reduceWakeupTime(wakeupTime, context);
return new RemoveRandomReturn(wakeupTime);
} else return new RemoveRandomReturn(item);
}
}
}
private RemoveRandomReturn removeRandomOneOnly(
RandomGrabArrayItemExclusionList excluding,
ClientContext context, long now) {
synchronized(root) {
long wakeupTime = Long.MAX_VALUE;
// Optimise the common case
RemoveRandomWithObject<T> rga = grabArrays[0];
if(logMINOR) Logger.minor(this, "Only one RGA: "+rga);
long excludeTime = rga.getWakeupTime(context, now);
if(excludeTime > 0)
return new RemoveRandomReturn(excludeTime);
if(rga == null) {
Logger.error(this, "Only one entry and that is null");
// We are sure
grabArrays = (C[]) new RemoveRandomWithObject[0];
grabClients = (T[]) new Object[0];
return null;
}
RemoveRandomReturn val = rga.removeRandom(excluding, context, now);
RandomGrabArrayItem item = null;
if(val != null) { // val == null => remove it
if(val.item != null)
item = val.item;
else {
wakeupTime = val.wakeupTime;
}
}
if(rga.isEmpty()) {
if(logMINOR)
Logger.minor(this, "Removing only grab array (0) : "+rga);
grabArrays = (C[]) new RemoveRandomWithObject[0];
grabClients = (T[]) new Object[0];
}
if(logMINOR)
Logger.minor(this, "Returning (one item only) "+item+" for "+rga);
if(item == null) {
if(grabArrays.length == 0) {
if(logMINOR) Logger.minor(this, "Arrays are empty on "+this);
return null; // Remove this as well
}
reduceWakeupTime(wakeupTime, context);
return new RemoveRandomReturn(wakeupTime);
} else return new RemoveRandomReturn(item);
}
}
private void removeElement(int x) {
synchronized(root) {
final int grabArraysLength = grabArrays.length;
int newLen = grabArraysLength > 1 ? grabArraysLength-1 : 0;
C[] newArray = (C[]) new RemoveRandomWithObject[newLen];
if(x > 0)
System.arraycopy(grabArrays, 0, newArray, 0, x);
if(x < grabArraysLength-1)
System.arraycopy(grabArrays, x+1, newArray, x, grabArraysLength - (x+1));
grabArrays = newArray;
Object[] newClients = new Object[newLen];
if(x > 0)
System.arraycopy(grabClients, 0, newClients, 0, x);
if(x < grabArraysLength-1)
System.arraycopy(grabClients, x+1, newClients, x, grabArraysLength - (x+1));
grabClients = (T[]) newClients;
}
}
public boolean isEmpty() {
synchronized(root) {
return grabArrays.length == 0;
}
}
public int size() {
synchronized(root) {
return grabArrays.length;
}
}
@Override
public void maybeRemove(RemoveRandom r, ClientContext context) {
int count = 0;
int finalSize;
synchronized(root) {
while(true) {
int found = -1;
for(int i=0;i<grabArrays.length;i++) {
if(grabArrays[i] == r) {
found = i;
break;
}
}
if(found != -1) {
count++;
if(count > 1) Logger.error(this, "Found "+r+" many times in "+this, new Exception("error"));
removeElement(found);
} else {
break;
}
}
finalSize = grabArrays.length;
}
if(count == 0) {
// This is not unusual, it was e.g. removed because of being empty.
// And it has already been removeFrom()'ed.
if(logMINOR) Logger.minor(this, "Not in parent: "+r+" for "+this, new Exception("error"));
}
if(finalSize == 0 && parent != null) {
parent.maybeRemove(this, context);
}
}
@Override
public void setParent(RemoveRandomParent newParent) {
synchronized(root) {
this.parent = newParent;
}
}
@Override
public RequestSelectionTreeNode getParentGrabArray() {
synchronized(root) {
return parent;
}
}
@Override
public long getWakeupTime(ClientContext context, long now) {
synchronized(root) {
if(wakeupTime < now) wakeupTime = 0;
return wakeupTime;
}
}
@Override
public boolean reduceWakeupTime(long wakeupTime, ClientContext context) {
if(logMINOR) Logger.minor(this, "reduceCooldownTime("+(wakeupTime-System.currentTimeMillis())+") on "+this);
boolean reachedRoot = false;
synchronized(root) {
if(this.wakeupTime > wakeupTime) {
this.wakeupTime = wakeupTime;
if(parent != null) parent.reduceWakeupTime(wakeupTime, context);
else reachedRoot = true; // Even if it reduces it we need to wake it up.
} else return false;
}
if(reachedRoot)
root.wakeUp(context);
return true;
}
@Override
public void clearWakeupTime(ClientContext context) {
if(logMINOR) Logger.minor(this, "clearCooldownTime() on "+this);
synchronized(root) {
wakeupTime = 0;
if(parent != null) parent.clearWakeupTime(context);
}
}
}