package com.limegroup.gnutella.altlocs; import org.limewire.core.settings.UploadSettings; import org.limewire.util.Objects; import com.limegroup.gnutella.RemoteFileDesc; import com.limegroup.gnutella.URN; import com.limegroup.gnutella.downloader.RemoteFileDescFactory; /** * Abstract super class that encompasses common functionality shared by most * implementations of {@link AlternateLocation}. */ public abstract class AbstractAlternateLocation implements AlternateLocation { /** * Constant for the sha1 urn for this <tt>AlternateLocation</tt> -- can be * <tt>null</tt>. */ protected final URN SHA1_URN; /** * Constant for the string to display as the httpStringValue. */ private String DISPLAY_STRING; /** * Cached hash code that is lazily initialized. */ protected volatile int hashCode = 0; /** * LOCKING: obtain this' monitor while changing/accessing _count and * _demoted as multiple threads could be accessing them. */ /** * Maintains a count of how many times this alternate location has been * seen. A value of 0 means this alternate location was failed one more time * that it has succeeded. Newly created AlternateLocations start out wit a * value of 1. */ protected volatile int _count = 0; /** * Two counter objects to keep track of altloc expiration. */ private final Average legacy, ping, response; protected AbstractAlternateLocation(URN sha1) { SHA1_URN = Objects.nonNull(sha1, "sha1"); legacy = new Average(); ping = new Average(); response = new Average(); } // ////////////////////////////accessors//////////////////////////// /* * (non-Javadoc) * * @see com.limegroup.gnutella.altlocs.AlternateLocation#getSHA1Urn() */ public URN getSHA1Urn() { return SHA1_URN; } /* * (non-Javadoc) * * @see com.limegroup.gnutella.altlocs.AlternateLocation#getCount() */ public synchronized int getCount() { return _count; } /* * (non-Javadoc) * * @see com.limegroup.gnutella.altlocs.AlternateLocation#isDemoted() */ public abstract boolean isDemoted(); // //////////////////////////Mesh utility // methods//////////////////////////// public String httpStringValue() { if (DISPLAY_STRING == null) DISPLAY_STRING = generateHTTPString(); return DISPLAY_STRING; } /* * (non-Javadoc) * * @see * com.limegroup.gnutella.altlocs.AlternateLocation#createRemoteFileDesc * (long) */ public abstract RemoteFileDesc createRemoteFileDesc(long size, RemoteFileDescFactory remoteFileDescFactory); /* * (non-Javadoc) * * @see com.limegroup.gnutella.altlocs.AlternateLocation#isMe() */ public abstract boolean isMe(); /* * (non-Javadoc) * * @see com.limegroup.gnutella.altlocs.AlternateLocation#increment() */ public synchronized void increment() { _count++; } /** * package access for demoting this. */ abstract void demote(); /** * package access for promoting this. */ abstract void promote(); /* * (non-Javadoc) * * @see com.limegroup.gnutella.altlocs.AlternateLocation#createClone() */ public abstract AlternateLocation createClone(); /* * (non-Javadoc) * * @see com.limegroup.gnutella.altlocs.AlternateLocation#send(long, int) */ public synchronized void send(long now, int meshType) { switch (meshType) { case MESH_LEGACY: legacy.send(now); return; case MESH_PING: ping.send(now); return; case MESH_RESPONSE: response.send(now); return; default: throw new IllegalArgumentException("unknown mesh type"); } } /* * (non-Javadoc) * * @see com.limegroup.gnutella.altlocs.AlternateLocation#canBeSent(int) */ public synchronized boolean canBeSent(int meshType) { switch (meshType) { case MESH_LEGACY: if (!UploadSettings.EXPIRE_LEGACY.getValue()) return true; return legacy.canBeSent(UploadSettings.LEGACY_BIAS.getValue(), UploadSettings.LEGACY_EXPIRATION_DAMPER.getValue()); case MESH_PING: if (!UploadSettings.EXPIRE_PING.getValue()) return true; return ping.canBeSent(UploadSettings.PING_BIAS.getValue(), UploadSettings.PING_EXPIRATION_DAMPER.getValue()); case MESH_RESPONSE: if (!UploadSettings.EXPIRE_RESPONSE.getValue()) return true; return response.canBeSent(UploadSettings.RESPONSE_BIAS.getValue(), UploadSettings.RESPONSE_EXPIRATION_DAMPER.getValue()); default: throw new IllegalArgumentException("unknown mesh type"); } } /* * (non-Javadoc) * * @see com.limegroup.gnutella.altlocs.AlternateLocation#canBeSentAny() */ public synchronized boolean canBeSentAny() { return canBeSent(MESH_LEGACY) || canBeSent(MESH_PING) || canBeSent(MESH_RESPONSE); } synchronized void resetSent() { ping.reset(); legacy.reset(); response.reset(); } // /////////////////////////////helpers//////////////////////////////// /** * Overrides the equals method to accurately compare * <tt>AlternateLocation</tt> instances. <tt>AlternateLocation</tt>s are * equal if their <tt>URL</tt>s are equal. * * @param obj the <tt>Object</tt> instance to compare to * @return <tt>true</tt> if the <tt>URL</tt> of this * <tt>AlternateLocation</tt> is equal to the <tt>URL</tt> of the * <tt>AlternateLocation</tt> location argument, and otherwise * returns <tt>false</tt> */ @Override public boolean equals(Object obj) { if (obj == this) return true; if (!(obj instanceof AbstractAlternateLocation)) return false; AbstractAlternateLocation other = (AbstractAlternateLocation) obj; return SHA1_URN.equals(other.SHA1_URN); } /** * The idea is that this is smaller than any AlternateLocation who has a * greater value of _count. There is one exception to this rule -- a demoted * AlternateLocation has a higher value irrespective of count. * <p> * This is because we want to have a sorted set of AlternateLocation where * any demoted AlternateLocation is put at the end of the list because it * probably does not work. * <p> * Further we want to get AlternateLocations with smaller counts to be * propogated more, since this will serve to get better load balancing of * uploader. */ public int compareTo(AlternateLocation other) { int ret = _count - other.getCount(); if (ret != 0) return ret; return ret; } protected abstract String generateHTTPString(); /** * Overrides the hashCode method of Object to meet the contract of hashCode. * Since we override equals, it is necessary to also override hashcode to * ensure that two "equal" alternate locations return the same hashCode, * less we unleash unknown havoc on the hash-based collections. * * @return a hash code value for this object */ @Override public int hashCode() { return 17 * 37 + this.SHA1_URN.hashCode(); } private static class Average { /** The number of times this altloc was given out */ private int numTimes; /** The average time in ms between giving out the altloc */ private double average; /** The last time the altloc was given out */ private long lastSentTime; /** The last calculated threshold, -1 if dirty */ private double cachedTreshold = -1; public void send(long now) { if (lastSentTime == 0) lastSentTime = now; average = ((average * numTimes) + (now - lastSentTime)) / ++numTimes; lastSentTime = now; cachedTreshold = -1; } public boolean canBeSent(float bias, float damper) { if (numTimes < 2 || average == 0) return true; if (cachedTreshold == -1) cachedTreshold = Math.abs(Math.log(average) / Math.log(damper)); return numTimes < cachedTreshold * bias; } public void reset() { numTimes = 0; average = 0; lastSentTime = 0; } } }