package com.limegroup.gnutella.udpconnect; import java.util.HashMap; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; /** * This class defines a DataWindow for sending or receiving data * using UDP with possible out of order data. Within a certain window * size, this data will be accepted. Data that has not been acknowledged, * will remain. For readers, the data can be passed on once any holes are * received. For the writer, if the round trip time for acks of the older data * is greatly exceeded, the data can be resent to try and receive an ack. * * All methods in this class rely on external synchronization of access. * * TODO: DataMessage timing still requires work. */ public class DataWindow { private static final Log LOG = LogFactory.getLog(DataWindow.class); static{ LOG.debug("log system initialized debug level"); } public static final int MAX_SEQUENCE_NUMBER = 0xFFFF; private static final int HIST_SIZE = 4; private static final float RTT_GAIN = 1.0f / 8.0f; private static final float DEVIATION_GAIN = 1.0f / 4.0f; private final HashMap window; private long windowStart; private int windowSize; private long averageRTT; private long averageLowRTT; private int lowRTTCount; private float srtt; private float rttvar; private float rto; /* * Define a data window for sending or receiving multiple udp packets * The size defines how much look ahead there is. Start is normally zero * or one. */ public DataWindow(int size, long start) { windowStart = start; windowSize = size; window = new HashMap(size+2); } /* * Add a new message to the window. */ public DataRecord addData(UDPConnectionMessage msg) { if (LOG.isDebugEnabled()) LOG.debug("adding message seq "+msg.getSequenceNumber()+ " window start "+windowStart); DataRecord d = new DataRecord(msg.getSequenceNumber(),msg); window.put(d.pkey, d); return d; } /** * Get the block based on the sequenceNumber. */ public DataRecord getBlock(long pnum) { return (DataRecord) window.get(new Long(pnum)); } /** * Get the start of the data window. The start will generally be the * sequence number of the lowest unacked message. */ public long getWindowStart() { return windowStart; } /** * Get the size of the data window. */ public int getWindowSize() { return windowSize; } /** * Get the number of slots in use. This excludes written data. */ public int getUsedSpots() { DataRecord d; Long pkey; int count = 0; for (long i = windowStart; i < windowStart+windowSize+3; i++) { pkey = new Long(i); // Count the spots that are full and not written if ( (d = (DataRecord) window.get(pkey)) != null && (!d.written || i != windowStart)) count++; } return(count); } /** * Get the number of slots available to be used. */ public int getWindowSpace() { return(windowSize - getUsedSpots()); } /** * Calculate the average wait time of the N lowest unresponded to * blocks */ public int calculateWaitTime(long time, int n) { DataRecord d; Long pkey; int count = 0; long totalDelta = 0; for (long i = windowStart; i < windowStart+windowSize+1; i++) { pkey = new Long(i); d = (DataRecord) window.get(pkey); if ( d != null && d.acks == 0 ) { count++; totalDelta += time - d.sentTime; if (count >= n) break; } } if (count > 0) return(((int)totalDelta)/count); else return 0; } /** * Clear out the acknowledged blocks at the beginning and advance the * window forward. Return the number of acked blocks. */ public int clearLowAckedBlocks() { DataRecord d; Long pkey; int count = 0; for (long i = windowStart; i < windowStart+windowSize+1; i++) { pkey = new Long(i); d = (DataRecord) window.get(pkey); if ( d != null && d.acks > 0 ) { window.remove(pkey); count++; } else { break; } } windowStart += count; return(count); } /** * From the window, find the number for the next block. * i.e. sequenceNumber */ public long getLowestUnsentBlock() { Long pkey; for (long i = windowStart; i < windowStart+windowSize+1; i++) { pkey = new Long(i); if (window.get(pkey) == null) return(i); } return(-1); } /** * Count the number of acks from higher number blocks. * This should give you a hint that a block went missing. * Note that this assumes that the low block isn't acked since * it would get cleared if it was acked. */ public int countHigherAckBlocks() { DataRecord d; Long pkey; int count = 0; for (long i = windowStart+1; i < windowStart+windowSize+1; i++) { pkey = new Long(i); d = (DataRecord) window.get(pkey); if ( d != null && d.acks > 0 ) { count++; } } return(count); } /** * If the sent data has not been acked for some multiple of * the RTO, it looks like a message was lost. */ public boolean acksAppearToBeMissing(long time, int multiple) { int irto = (int)rto; // Check for first record being old DataRecord drec = getBlock(windowStart); if ( irto > 0 && drec != null && drec.acks < 1 && drec.sentTime + (multiple * irto) < time ) { return true; } return false; } /** * Return the RTO based on window data and acks. */ public int getRTO() { return (int)rto; } /** * Return the rttvar which is a measure of the range of rtt values */ public float getRTTVar() { return rttvar; } /** * Return the srtt estimate */ public float getSRTT() { return srtt; } /** * Return the current measure of low round trip time. */ public int lowRoundTripTime() { return (int) averageLowRTT; } /** * Record that a block was acked and calculate the * round trip time and averages from it. */ public void ackBlock(long pnum) { if (LOG.isDebugEnabled()) LOG.debug("entered ackBlock with # "+pnum); DataRecord drec = getBlock(pnum); if ( drec != null ) { drec.acks++; drec.ackTime = System.currentTimeMillis(); // delta = measuredRTT - srtt // srtt = srtt + g * delta // rttvar = rttvar + h*(abs(delta) - rttvar) // RTO = srtt + 4 * rttvar // delta is the difference between the measured RTT // and the current smoothed RTT estimator (srtt). // g is the gain applied to the RTT estimator and equals // 1/8. h is the gain applied to the mean deviation estimator // and equals 1/4. // Add to the averageRTT if ( drec.acks == 1 && drec.sends == 1 ) { long rtt = (drec.ackTime-drec.sentTime); float delta = ((float) rtt) - srtt; if ( rtt > 0 ) { // Compute RTO if ( srtt <= 0.1 ) srtt = delta; else srtt = srtt + RTT_GAIN * delta; rttvar = rttvar + DEVIATION_GAIN*(Math.abs(delta) - rttvar); rto = (float)(srtt + 4 * rttvar + 0.5); // Compute the average RTT if ( averageRTT == 0 ) averageRTT = rtt; else { float avgRTT = ((float)(averageRTT*(HIST_SIZE-1)+rtt))/HIST_SIZE; averageRTT = (long) avgRTT; } // Compute a measure of the lowest RTT if ( lowRTTCount < 10 || rtt < averageLowRTT ) { if ( averageLowRTT == 0 ) averageLowRTT = rtt; else { float lowRtt = ((float)(averageLowRTT*(HIST_SIZE-1)+rtt)) /HIST_SIZE; averageLowRTT = (long)lowRtt; } lowRTTCount++; } } } } } /** * Record an ack if not yet present for blocks up to the receiving * windowStart sent from the receiving connection. */ public void pseudoAckToReceiverWindow(long wStart) { // If the windowStart is old, just ignore it if ( wStart <= windowStart ) return; DataRecord drec; for (long i = windowStart; i < wStart; i++) { drec = getBlock(i); if ( drec != null && drec.acks == 0) { // Presumably the ack got lost or is still incoming so ack it drec.acks++; // Create a fake ackTime since we don't know when it should be drec.ackTime = drec.sentTime + (int)rto; } } } /** * Get the oldest unacked block. */ public DataRecord getOldestUnackedBlock() { DataRecord d; // Find the oldest block. DataRecord oldest = null; for (long i = windowStart; i < windowStart+windowSize+1; i++) { d = getBlock(i); if ( d != null ) { if ( d.acks == 0 && (oldest == null || d.sentTime < oldest.sentTime) ) { oldest = d; } } } return oldest; } /** * Get a writable block which means unwritten ones at the start of Window */ public DataRecord getWritableBlock() { if (LOG.isDebugEnabled()) LOG.debug("entered getWritableBlock wStart "+windowStart+" wSize "+windowSize); DataRecord d; // Find a writable block for (long i = windowStart; i < windowStart+windowSize+1; i++) { d = getBlock(i); if ( d != null ) { LOG.debug("current block not null"); if (d.written) { LOG.debug("current block is written"); continue; } else { LOG.debug("returning a block"); return d; } } else { LOG.debug("log is null"); break; } } LOG.debug("returning null"); return null; } /** * To advance the window of the reader, higher blocks need to come in. * Once they do, older written blocks below the new window can be cleared. * Return the size of the window advancement. */ public int clearEarlyWrittenBlocks() { DataRecord d; Long pkey; int count = 0; long maxBlock = windowStart+windowSize; long newMaxBlock = maxBlock+windowSize; long lastBlock = -1; // Find the last block /* for (int i = maxBlock; i < newMaxBlock; i++) { d = getBlock(i); if ( d != null ) lastBlock = i; } */ // Advance the window up to windowSize before lastBlock and clear old // blocks - This ensures that the data is successfully acked before // it is removed. Note: windowSpace must reflect the true // potential space. //for (int i = windowStart; i < lastBlock - windowSize + 1; i++) { for (long i = windowStart; i < windowStart + windowSize + 1; i++) { pkey = new Long(i); d = (DataRecord) window.get(pkey); if ( d != null && d.written) { window.remove(pkey); count++; } else { break; } } windowStart += count; return(count); } /** * Find the record that has been acked the most. */ public DataRecord findMostAcked() { DataRecord d; DataRecord mostAcked = null; // Compare ack numbers for (long i = windowStart; i < windowStart+windowSize+1; i++) { d = getBlock(i); if ( mostAcked == null ) { mostAcked = d; } else if ( d != null ) { if (mostAcked.acks < d.acks) mostAcked = d; } } return mostAcked; } /** * Find the number of unwritten records */ public int numNotWritten() { DataRecord d; int count = 0; // Count the number of records not written for (long i = windowStart; i < windowStart+windowSize+1; i++) { d = getBlock(i); if ( d != null && !d.written) { count++; } } return count; } /** * Find the number of unacked records */ public int numNotAcked() { DataRecord d; int count = 0; // Count the number of records not acked for (long i = windowStart; i < windowStart+windowSize+1; i++) { d = getBlock(i); if ( d != null && d.acks <=0) { count++; } } return count; } public void printFinalStats() { System.out.println( " avgRTT:"+averageRTT+ " lowRTT:"+averageLowRTT); } } /** * Record information about data messages either getting written to the * network or getting read from the network. In the first case, the * acks is important. In the second case, the written state is important. * For writing, the sentTime and the ackTime form the basis for the * round trip time and a calculation for timeout resends. */ class DataRecord { final Long pkey; // sequence number as a Long final UDPConnectionMessage msg; // the actual data message int sends; // count of the sends boolean written; // whether the data was written int acks; // count of the number of acks long sentTime; // when it was sent long ackTime; // when it was acked DataRecord(long pnum, UDPConnectionMessage msg) { pkey = new Long(pnum); this.msg=msg; } }