mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-19 14:19:53 +01:00
Back off between attempts to raise connection limit.
This commit is contained in:
@@ -19,7 +19,14 @@ interface BluetoothConnectionLimiter {
|
|||||||
* This is longer than {@link #STABILITY_PERIOD_MS} so we don't start
|
* This is longer than {@link #STABILITY_PERIOD_MS} so we don't start
|
||||||
* another attempt before knowing the outcome of the last one.
|
* another attempt before knowing the outcome of the last one.
|
||||||
*/
|
*/
|
||||||
long MIN_ATTEMPT_INTERVAL_MS = MINUTES.toMillis(5);
|
long MIN_ATTEMPT_INTERVAL_MS = MINUTES.toMillis(2);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The maximum exponent to use when backing off between attempts to raise
|
||||||
|
* the connection limit. The maximum interval between attempts is
|
||||||
|
* MIN_ATTEMPT_INTERVAL_MS * (1L << MAX_ATTEMPT_BACKOFF) =~ 34 hours.
|
||||||
|
*/
|
||||||
|
int MAX_ATTEMPT_BACKOFF = 10;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Informs the limiter that key agreement has started.
|
* Informs the limiter that key agreement has started.
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import javax.annotation.concurrent.GuardedBy;
|
|||||||
import javax.annotation.concurrent.ThreadSafe;
|
import javax.annotation.concurrent.ThreadSafe;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import static java.lang.Math.min;
|
||||||
import static java.util.logging.Level.INFO;
|
import static java.util.logging.Level.INFO;
|
||||||
import static java.util.logging.Level.WARNING;
|
import static java.util.logging.Level.WARNING;
|
||||||
import static java.util.logging.Logger.getLogger;
|
import static java.util.logging.Logger.getLogger;
|
||||||
@@ -38,9 +39,10 @@ class BluetoothConnectionLimiterImpl implements BluetoothConnectionLimiter {
|
|||||||
@GuardedBy("lock")
|
@GuardedBy("lock")
|
||||||
private boolean keyAgreementInProgress = false;
|
private boolean keyAgreementInProgress = false;
|
||||||
@GuardedBy("lock")
|
@GuardedBy("lock")
|
||||||
private int connectionLimit = 1;
|
private int connectionLimit = 1, attemptBackoff = 0;
|
||||||
@GuardedBy("lock")
|
@GuardedBy("lock")
|
||||||
private long timeOfLastAttempt = 0;
|
private long timeOfLastAttempt = 0,
|
||||||
|
attemptInterval = MIN_ATTEMPT_INTERVAL_MS;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
BluetoothConnectionLimiterImpl(EventBus eventBus, Clock clock) {
|
BluetoothConnectionLimiterImpl(EventBus eventBus, Clock clock) {
|
||||||
@@ -123,10 +125,14 @@ class BluetoothConnectionLimiterImpl implements BluetoothConnectionLimiter {
|
|||||||
public void connectionClosed(DuplexTransportConnection conn,
|
public void connectionClosed(DuplexTransportConnection conn,
|
||||||
boolean exception) {
|
boolean exception) {
|
||||||
synchronized (lock) {
|
synchronized (lock) {
|
||||||
|
int numConnections = connections.size();
|
||||||
Iterator<ConnectionRecord> it = connections.iterator();
|
Iterator<ConnectionRecord> it = connections.iterator();
|
||||||
while (it.hasNext()) {
|
while (it.hasNext()) {
|
||||||
if (it.next().connection == conn) {
|
if (it.next().connection == conn) {
|
||||||
it.remove();
|
it.remove();
|
||||||
|
if (exception && numConnections > connectionLimit) {
|
||||||
|
connectionFailedAboveLimit();
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -152,7 +158,7 @@ class BluetoothConnectionLimiterImpl implements BluetoothConnectionLimiter {
|
|||||||
} else if (connections.size() < connectionLimit) {
|
} else if (connections.size() < connectionLimit) {
|
||||||
LOG.info("Allowing contact connection, below limit");
|
LOG.info("Allowing contact connection, below limit");
|
||||||
return true;
|
return true;
|
||||||
} else if (canAttemptToRaiseLimit(now)) {
|
} else if (now - timeOfLastAttempt >= attemptInterval) {
|
||||||
LOG.info("Allowing contact connection, at limit");
|
LOG.info("Allowing contact connection, at limit");
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
@@ -170,6 +176,8 @@ class BluetoothConnectionLimiterImpl implements BluetoothConnectionLimiter {
|
|||||||
if (stable > connectionLimit) {
|
if (stable > connectionLimit) {
|
||||||
LOG.info("Raising connection limit");
|
LOG.info("Raising connection limit");
|
||||||
connectionLimit++;
|
connectionLimit++;
|
||||||
|
attemptBackoff = 0;
|
||||||
|
attemptInterval = MIN_ATTEMPT_INTERVAL_MS;
|
||||||
}
|
}
|
||||||
if (LOG.isLoggable(INFO)) {
|
if (LOG.isLoggable(INFO)) {
|
||||||
LOG.info(stable + " connections are stable, limit is "
|
LOG.info(stable + " connections are stable, limit is "
|
||||||
@@ -178,8 +186,13 @@ class BluetoothConnectionLimiterImpl implements BluetoothConnectionLimiter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@GuardedBy("lock")
|
@GuardedBy("lock")
|
||||||
private boolean canAttemptToRaiseLimit(long now) {
|
private void connectionFailedAboveLimit() {
|
||||||
return now - timeOfLastAttempt >= MIN_ATTEMPT_INTERVAL_MS;
|
long now = clock.currentTimeMillis();
|
||||||
|
if (now - timeOfLastAttempt < STABILITY_PERIOD_MS) {
|
||||||
|
LOG.info("Connection failed above limit, increasing interval");
|
||||||
|
attemptBackoff = min(attemptBackoff + 1, MAX_ATTEMPT_BACKOFF);
|
||||||
|
attemptInterval = MIN_ATTEMPT_INTERVAL_MS * (1L << attemptBackoff);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final class ConnectionRecord {
|
private static final class ConnectionRecord {
|
||||||
|
|||||||
@@ -4,12 +4,17 @@ import org.briarproject.bramble.api.event.EventBus;
|
|||||||
import org.briarproject.bramble.api.plugin.TransportConnectionReader;
|
import org.briarproject.bramble.api.plugin.TransportConnectionReader;
|
||||||
import org.briarproject.bramble.api.plugin.TransportConnectionWriter;
|
import org.briarproject.bramble.api.plugin.TransportConnectionWriter;
|
||||||
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
|
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
|
||||||
|
import org.briarproject.bramble.api.sync.event.CloseSyncConnectionsEvent;
|
||||||
import org.briarproject.bramble.api.system.Clock;
|
import org.briarproject.bramble.api.system.Clock;
|
||||||
import org.briarproject.bramble.test.BrambleMockTestCase;
|
import org.briarproject.bramble.test.BrambleMockTestCase;
|
||||||
|
import org.briarproject.bramble.test.SettableClock;
|
||||||
import org.jmock.Expectations;
|
import org.jmock.Expectations;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.util.concurrent.atomic.AtomicLong;
|
||||||
|
|
||||||
|
import static org.briarproject.bramble.plugin.bluetooth.BluetoothConnectionLimiter.MIN_ATTEMPT_INTERVAL_MS;
|
||||||
import static org.briarproject.bramble.plugin.bluetooth.BluetoothConnectionLimiter.STABILITY_PERIOD_MS;
|
import static org.briarproject.bramble.plugin.bluetooth.BluetoothConnectionLimiter.STABILITY_PERIOD_MS;
|
||||||
import static org.junit.Assert.assertFalse;
|
import static org.junit.Assert.assertFalse;
|
||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
@@ -17,7 +22,6 @@ import static org.junit.Assert.assertTrue;
|
|||||||
public class BluetoothConnectionLimiterImplTest extends BrambleMockTestCase {
|
public class BluetoothConnectionLimiterImplTest extends BrambleMockTestCase {
|
||||||
|
|
||||||
private final EventBus eventBus = context.mock(EventBus.class);
|
private final EventBus eventBus = context.mock(EventBus.class);
|
||||||
private final Clock clock = context.mock(Clock.class);
|
|
||||||
|
|
||||||
private final DuplexTransportConnection conn1 =
|
private final DuplexTransportConnection conn1 =
|
||||||
context.mock(DuplexTransportConnection.class, "conn1");
|
context.mock(DuplexTransportConnection.class, "conn1");
|
||||||
@@ -32,117 +36,158 @@ public class BluetoothConnectionLimiterImplTest extends BrambleMockTestCase {
|
|||||||
|
|
||||||
private final long now = System.currentTimeMillis();
|
private final long now = System.currentTimeMillis();
|
||||||
|
|
||||||
|
private AtomicLong time;
|
||||||
private BluetoothConnectionLimiter limiter;
|
private BluetoothConnectionLimiter limiter;
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setUp() {
|
public void setUp() {
|
||||||
|
time = new AtomicLong(now);
|
||||||
|
Clock clock = new SettableClock(time);
|
||||||
limiter = new BluetoothConnectionLimiterImpl(eventBus, clock);
|
limiter = new BluetoothConnectionLimiterImpl(eventBus, clock);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testLimiterDoesNotAllowContactConnectionsDuringKeyAgreement() {
|
||||||
|
assertTrue(limiter.canOpenContactConnection());
|
||||||
|
|
||||||
|
expectCloseSyncConnectionsEvent();
|
||||||
|
limiter.keyAgreementStarted();
|
||||||
|
|
||||||
|
assertFalse(limiter.canOpenContactConnection());
|
||||||
|
|
||||||
|
limiter.keyAgreementEnded();
|
||||||
|
|
||||||
|
assertTrue(limiter.canOpenContactConnection());
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testLimiterAllowsAttemptToRaiseLimitAtStartup()
|
public void testLimiterAllowsAttemptToRaiseLimitAtStartup()
|
||||||
throws Exception {
|
throws Exception {
|
||||||
// First outgoing connection is allowed - we're below the limit
|
// First outgoing connection is allowed - we're below the limit of 1
|
||||||
expectGetCurrentTime(now);
|
|
||||||
assertTrue(limiter.canOpenContactConnection());
|
assertTrue(limiter.canOpenContactConnection());
|
||||||
expectGetCurrentTime(now);
|
|
||||||
assertTrue(limiter.contactConnectionOpened(conn1));
|
assertTrue(limiter.contactConnectionOpened(conn1));
|
||||||
|
|
||||||
// Second outgoing connection is allowed - it's time to try raising
|
// Second outgoing connection is allowed - it's time to try raising
|
||||||
// the limit
|
// the limit to 2
|
||||||
expectGetCurrentTime(now);
|
|
||||||
assertTrue(limiter.canOpenContactConnection());
|
assertTrue(limiter.canOpenContactConnection());
|
||||||
expectGetCurrentTime(now);
|
|
||||||
assertTrue(limiter.contactConnectionOpened(conn2));
|
assertTrue(limiter.contactConnectionOpened(conn2));
|
||||||
|
|
||||||
// Third outgoing connection is not allowed - we're above the limit
|
// Third outgoing connection is not allowed - we're above the limit of 1
|
||||||
expectGetCurrentTime(now);
|
|
||||||
assertFalse(limiter.canOpenContactConnection());
|
assertFalse(limiter.canOpenContactConnection());
|
||||||
|
|
||||||
// Third incoming connection is not allowed - we're above the limit
|
// Third incoming connection is not allowed - we're above the limit of 1
|
||||||
expectGetCurrentTime(now);
|
|
||||||
expectCloseConnection(conn3);
|
expectCloseConnection(conn3);
|
||||||
assertFalse(limiter.contactConnectionOpened(conn3));
|
assertFalse(limiter.contactConnectionOpened(conn3));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testLimiterAllowsThirdConnectionAfterFirstTwoAreClosed() {
|
public void testLimiterAllowsThirdConnectionAfterFirstTwoAreClosed() {
|
||||||
// First outgoing connection is allowed - we're below the limit
|
// First outgoing connection is allowed - we're below the limit of 1
|
||||||
expectGetCurrentTime(now);
|
|
||||||
assertTrue(limiter.canOpenContactConnection());
|
assertTrue(limiter.canOpenContactConnection());
|
||||||
expectGetCurrentTime(now);
|
|
||||||
assertTrue(limiter.contactConnectionOpened(conn1));
|
assertTrue(limiter.contactConnectionOpened(conn1));
|
||||||
|
|
||||||
// Second outgoing connection is allowed - it's time to try raising
|
// Second outgoing connection is allowed - it's time to try raising
|
||||||
// the limit
|
// the limit to 2
|
||||||
expectGetCurrentTime(now);
|
|
||||||
assertTrue(limiter.canOpenContactConnection());
|
assertTrue(limiter.canOpenContactConnection());
|
||||||
expectGetCurrentTime(now);
|
|
||||||
assertTrue(limiter.contactConnectionOpened(conn2));
|
assertTrue(limiter.contactConnectionOpened(conn2));
|
||||||
|
|
||||||
// Third outgoing connection is not allowed - we're above the limit
|
// Third outgoing connection is not allowed - we're above the limit of 1
|
||||||
expectGetCurrentTime(now);
|
|
||||||
assertFalse(limiter.canOpenContactConnection());
|
assertFalse(limiter.canOpenContactConnection());
|
||||||
|
|
||||||
// Close the first connection
|
// Close the first connection
|
||||||
limiter.connectionClosed(conn1, false);
|
limiter.connectionClosed(conn1, false);
|
||||||
|
|
||||||
// Third outgoing connection is not allowed - we're at the limit
|
// Third outgoing connection is not allowed - we're at the limit of 1
|
||||||
expectGetCurrentTime(now);
|
|
||||||
assertFalse(limiter.canOpenContactConnection());
|
assertFalse(limiter.canOpenContactConnection());
|
||||||
|
|
||||||
// Close the second connection
|
// Close the second connection
|
||||||
limiter.connectionClosed(conn2, false);
|
limiter.connectionClosed(conn2, false);
|
||||||
|
|
||||||
// Third outgoing connection is allowed - we're below the limit
|
// Third outgoing connection is allowed - we're below the limit of 1
|
||||||
expectGetCurrentTime(now);
|
|
||||||
assertTrue(limiter.canOpenContactConnection());
|
assertTrue(limiter.canOpenContactConnection());
|
||||||
expectGetCurrentTime(now);
|
|
||||||
assertTrue(limiter.contactConnectionOpened(conn3));
|
assertTrue(limiter.contactConnectionOpened(conn3));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testLimiterRaisesLimitWhenConnectionsAreStable() {
|
public void testLimiterRaisesLimitWhenConnectionsAreStable() {
|
||||||
// First outgoing connection is allowed - we're below the limit
|
// First outgoing connection is allowed - we're below the limit of 1
|
||||||
expectGetCurrentTime(now);
|
|
||||||
assertTrue(limiter.canOpenContactConnection());
|
assertTrue(limiter.canOpenContactConnection());
|
||||||
expectGetCurrentTime(now);
|
|
||||||
assertTrue(limiter.contactConnectionOpened(conn1));
|
assertTrue(limiter.contactConnectionOpened(conn1));
|
||||||
|
|
||||||
// Second outgoing connection is allowed - it's time to try raising
|
// Second outgoing connection is allowed - it's time to try raising
|
||||||
// the limit
|
// the limit to 2
|
||||||
expectGetCurrentTime(now);
|
|
||||||
assertTrue(limiter.canOpenContactConnection());
|
assertTrue(limiter.canOpenContactConnection());
|
||||||
expectGetCurrentTime(now);
|
|
||||||
assertTrue(limiter.contactConnectionOpened(conn2));
|
assertTrue(limiter.contactConnectionOpened(conn2));
|
||||||
|
|
||||||
// Third outgoing connection is not allowed - we're above the limit
|
// Third outgoing connection is not allowed - we're above the limit of 1
|
||||||
expectGetCurrentTime(now);
|
|
||||||
assertFalse(limiter.canOpenContactConnection());
|
assertFalse(limiter.canOpenContactConnection());
|
||||||
|
|
||||||
// Third outgoing connection is still not allowed - first two are
|
// Time passes
|
||||||
// stable but we're still at the limit
|
time.set(now + STABILITY_PERIOD_MS);
|
||||||
expectGetCurrentTime(now + STABILITY_PERIOD_MS);
|
|
||||||
|
// Third outgoing connection is still not allowed - first two are now
|
||||||
|
// stable so limit is raised to 2, but we're already at the new limit
|
||||||
assertFalse(limiter.canOpenContactConnection());
|
assertFalse(limiter.canOpenContactConnection());
|
||||||
|
|
||||||
// Close the first connection
|
// Time passes
|
||||||
limiter.connectionClosed(conn1, false);
|
time.set(now + MIN_ATTEMPT_INTERVAL_MS);
|
||||||
|
|
||||||
// Third outgoing connection is allowed - we're below the new limit
|
// Third outgoing connection is allowed - it's time to try raising
|
||||||
expectGetCurrentTime(now + STABILITY_PERIOD_MS);
|
// the limit to 3
|
||||||
assertTrue(limiter.canOpenContactConnection());
|
assertTrue(limiter.canOpenContactConnection());
|
||||||
expectGetCurrentTime(now + STABILITY_PERIOD_MS);
|
|
||||||
assertTrue(limiter.contactConnectionOpened(conn3));
|
assertTrue(limiter.contactConnectionOpened(conn3));
|
||||||
|
|
||||||
// Fourth outgoing connection is not allowed
|
// Fourth outgoing connection is not allowed - we're above the limit
|
||||||
expectGetCurrentTime(now + STABILITY_PERIOD_MS);
|
// of 2
|
||||||
assertFalse(limiter.canOpenContactConnection());
|
assertFalse(limiter.canOpenContactConnection());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void expectGetCurrentTime(long now) {
|
@Test
|
||||||
|
public void testLimiterIncreasesIntervalWhenConnectionFailsAboveLimit() {
|
||||||
|
// First outgoing connection is allowed - we're below the limit of 1
|
||||||
|
assertTrue(limiter.canOpenContactConnection());
|
||||||
|
assertTrue(limiter.contactConnectionOpened(conn1));
|
||||||
|
|
||||||
|
// Time passes
|
||||||
|
time.set(now + 1);
|
||||||
|
|
||||||
|
// Second outgoing connection is allowed - it's time to try raising
|
||||||
|
// the limit to 2
|
||||||
|
assertTrue(limiter.canOpenContactConnection());
|
||||||
|
assertTrue(limiter.contactConnectionOpened(conn2));
|
||||||
|
|
||||||
|
// Time passes - the first connection is stable, the second isn't
|
||||||
|
time.set(now + STABILITY_PERIOD_MS);
|
||||||
|
|
||||||
|
// First connection fails. The second connection isn't stable yet, so
|
||||||
|
// the limiter considers this a failed attempt and doubles the interval
|
||||||
|
// between attempts
|
||||||
|
limiter.connectionClosed(conn1, true);
|
||||||
|
|
||||||
|
// Third outgoing connection is not allowed - we're still at the limit
|
||||||
|
// of 1
|
||||||
|
assertFalse(limiter.canOpenContactConnection());
|
||||||
|
|
||||||
|
// Time passes - nearly time for the second attempt
|
||||||
|
time.set(now + MIN_ATTEMPT_INTERVAL_MS * 2);
|
||||||
|
|
||||||
|
// Third outgoing connection is not allowed - we're still at the limit
|
||||||
|
// of 1
|
||||||
|
assertFalse(limiter.canOpenContactConnection());
|
||||||
|
|
||||||
|
// Time passes - now it's time for the second attempt
|
||||||
|
time.set(now + 1 + MIN_ATTEMPT_INTERVAL_MS * 2);
|
||||||
|
|
||||||
|
// Third outgoing connection is allowed - it's time to try raising the
|
||||||
|
// limit to 2 again
|
||||||
|
assertTrue(limiter.canOpenContactConnection());
|
||||||
|
assertTrue(limiter.contactConnectionOpened(conn3));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void expectCloseSyncConnectionsEvent() {
|
||||||
context.checking(new Expectations() {{
|
context.checking(new Expectations() {{
|
||||||
oneOf(clock).currentTimeMillis();
|
oneOf(eventBus).broadcast(with(any(
|
||||||
will(returnValue(now));
|
CloseSyncConnectionsEvent.class)));
|
||||||
}});
|
}});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user