Refactored exponential backoff code out of JdbcDatabase and added tests.

This commit is contained in:
akwizgran
2013-02-06 23:51:23 +00:00
parent 379d6ed220
commit 3af077a4d8
4 changed files with 96 additions and 13 deletions

View File

@@ -0,0 +1,30 @@
package net.sf.briar.db;
class ExponentialBackoff {
/**
* Returns the expiry time of a packet transmitted at time <tt>now</tt>
* over a transport with maximum latency <tt>maxLatency</tt>, where the
* packet has previously been transmitted <tt>txCount</tt> times. All times
* are in milliseconds. The expiry time is
* <tt>now + maxLatency * 2 ^ (txCount + 1)</tt>, so the interval between
* transmissions increases exponentially. If the expiry time would
* be greater than Long.MAX_VALUE, Long.MAX_VALUE is returned.
*/
static long calculateExpiry(long now, long maxLatency, int txCount) {
if(now < 0) throw new IllegalArgumentException();
if(maxLatency <= 0) throw new IllegalArgumentException();
if(txCount < 0) throw new IllegalArgumentException();
// The maximum round-trip time is twice the maximum latency
long roundTrip = maxLatency * 2;
if(roundTrip < 0) return Long.MAX_VALUE;
// The interval between transmissions is roundTrip * 2 ^ txCount
for(int i = 0; i < txCount; i++) {
roundTrip <<= 1;
if(roundTrip < 0) return Long.MAX_VALUE;
}
// The expiry time is the current time plus the interval
long expiry = now + roundTrip;
return expiry < 0 ? Long.MAX_VALUE : expiry;
}
}

View File

@@ -5,6 +5,7 @@ import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
import static net.sf.briar.api.Rating.UNRATED;
import static net.sf.briar.db.DatabaseConstants.RETENTION_MODULUS;
import static net.sf.briar.db.ExponentialBackoff.calculateExpiry;
import java.io.File;
import java.io.FileNotFoundException;
@@ -2877,17 +2878,4 @@ abstract class JdbcDatabase implements Database<Connection> {
throw new DbException(e);
}
}
// FIXME: Refactor the exponential backoff logic into a separate class
private long calculateExpiry(long now, long maxLatency, int txCount) {
long roundTrip = maxLatency * 2;
if(roundTrip < 0) return Long.MAX_VALUE;
for(int i = 0; i < txCount; i++) {
roundTrip <<= 1;
if(roundTrip < 0) return Long.MAX_VALUE;
}
long expiry = now + roundTrip;
if(expiry < 0) return Long.MAX_VALUE;
return expiry;
}
}

View File

@@ -77,6 +77,7 @@
<test name='net.sf.briar.db.BasicH2Test'/>
<test name='net.sf.briar.db.DatabaseCleanerImplTest'/>
<test name='net.sf.briar.db.DatabaseComponentImplTest'/>
<test name='net.sf.briar.db.ExponentialBackoffTest'/>
<test name='net.sf.briar.lifecycle.ShutdownManagerImplTest'/>
<test name='net.sf.briar.lifecycle.WindowsShutdownManagerImplTest'/>
<test name='net.sf.briar.messaging.ConstantsTest'/>

View File

@@ -0,0 +1,64 @@
package net.sf.briar.db;
import net.sf.briar.BriarTestCase;
import org.junit.Test;
public class ExponentialBackoffTest extends BriarTestCase {
private static final int ONE_HOUR = 60 * 60 * 1000;
@Test
public void testFirstIntervalEqualsRoundTripTime() {
long first = ExponentialBackoff.calculateExpiry(0, ONE_HOUR, 0);
assertEquals(2 * ONE_HOUR, first);
}
@Test
public void testIntervalsIncreaseExponentially() {
long first = ExponentialBackoff.calculateExpiry(0, ONE_HOUR, 0);
long second = ExponentialBackoff.calculateExpiry(0, ONE_HOUR, 1);
long third = ExponentialBackoff.calculateExpiry(0, ONE_HOUR, 2);
long fourth = ExponentialBackoff.calculateExpiry(0, ONE_HOUR, 3);
assertEquals(third, fourth / 2);
assertEquals(second, third / 2);
assertEquals(first, second / 2);
}
@Test
public void testIntervalIsAddedToCurrentTime() {
long now = System.currentTimeMillis();
long fromZero = ExponentialBackoff.calculateExpiry(0, ONE_HOUR, 0);
long fromNow = ExponentialBackoff.calculateExpiry(now, ONE_HOUR, 0);
assertEquals(now, fromNow - fromZero);
}
@Test
public void testRoundTripTimeOverflow() {
long maxLatency = Long.MAX_VALUE / 2 + 1; // RTT will overflow
long expiry = ExponentialBackoff.calculateExpiry(0, maxLatency, 0);
assertEquals(Long.MAX_VALUE, expiry); // Overflow caught
}
@Test
public void testTransmissionCountOverflow() {
long maxLatency = (Long.MAX_VALUE - 1) / 2; // RTT will not overflow
long expiry = ExponentialBackoff.calculateExpiry(0, maxLatency, 0);
assertEquals(Long.MAX_VALUE - 1, expiry); // No overflow
expiry = ExponentialBackoff.calculateExpiry(0, maxLatency, 1);
assertEquals(Long.MAX_VALUE, expiry); // Overflow caught
expiry = ExponentialBackoff.calculateExpiry(0, maxLatency, 2);
assertEquals(Long.MAX_VALUE, expiry); // Overflow caught
}
@Test
public void testCurrentTimeOverflow() {
long maxLatency = (Long.MAX_VALUE - 1) / 2; // RTT will not overflow
long expiry = ExponentialBackoff.calculateExpiry(0, maxLatency, 0);
assertEquals(Long.MAX_VALUE - 1, expiry); // No overflow
expiry = ExponentialBackoff.calculateExpiry(1, maxLatency, 0);
assertEquals(Long.MAX_VALUE, expiry); // No overflow
expiry = ExponentialBackoff.calculateExpiry(2, maxLatency, 0);
assertEquals(Long.MAX_VALUE, expiry); // Overflow caught
}
}