Reschedule polling when connections are opened or closed.

This commit is contained in:
akwizgran
2016-05-05 16:10:18 +01:00
parent 1bdd7ca761
commit e3bf20aed5
3 changed files with 198 additions and 35 deletions

View File

@@ -7,6 +7,7 @@ import org.briarproject.api.plugins.BackoffFactory;
import org.briarproject.api.plugins.ConnectionManager; import org.briarproject.api.plugins.ConnectionManager;
import org.briarproject.api.plugins.ConnectionRegistry; import org.briarproject.api.plugins.ConnectionRegistry;
import org.briarproject.api.plugins.PluginManager; import org.briarproject.api.plugins.PluginManager;
import org.briarproject.api.system.Clock;
import java.security.SecureRandom; import java.security.SecureRandom;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
@@ -39,9 +40,9 @@ public class PluginsModule {
ScheduledExecutorService scheduler, ScheduledExecutorService scheduler,
ConnectionManager connectionManager, ConnectionManager connectionManager,
ConnectionRegistry connectionRegistry, PluginManager pluginManager, ConnectionRegistry connectionRegistry, PluginManager pluginManager,
SecureRandom random, EventBus eventBus) { SecureRandom random, Clock clock, EventBus eventBus) {
Poller poller = new Poller(ioExecutor, scheduler, connectionManager, Poller poller = new Poller(ioExecutor, scheduler, connectionManager,
connectionRegistry, pluginManager, random); connectionRegistry, pluginManager, random, clock);
eventBus.addListener(poller); eventBus.addListener(poller);
return poller; return poller;
} }

View File

@@ -3,6 +3,7 @@ package org.briarproject.plugins;
import org.briarproject.api.TransportId; import org.briarproject.api.TransportId;
import org.briarproject.api.contact.ContactId; import org.briarproject.api.contact.ContactId;
import org.briarproject.api.event.ConnectionClosedEvent; import org.briarproject.api.event.ConnectionClosedEvent;
import org.briarproject.api.event.ConnectionOpenedEvent;
import org.briarproject.api.event.ContactStatusChangedEvent; import org.briarproject.api.event.ContactStatusChangedEvent;
import org.briarproject.api.event.Event; import org.briarproject.api.event.Event;
import org.briarproject.api.event.EventListener; import org.briarproject.api.event.EventListener;
@@ -16,12 +17,15 @@ import org.briarproject.api.plugins.TransportConnectionWriter;
import org.briarproject.api.plugins.duplex.DuplexPlugin; import org.briarproject.api.plugins.duplex.DuplexPlugin;
import org.briarproject.api.plugins.duplex.DuplexTransportConnection; import org.briarproject.api.plugins.duplex.DuplexTransportConnection;
import org.briarproject.api.plugins.simplex.SimplexPlugin; import org.briarproject.api.plugins.simplex.SimplexPlugin;
import org.briarproject.api.system.Clock;
import java.security.SecureRandom; import java.security.SecureRandom;
import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.logging.Logger; import java.util.logging.Logger;
import javax.inject.Inject; import javax.inject.Inject;
@@ -39,20 +43,24 @@ class Poller implements EventListener {
private final ConnectionRegistry connectionRegistry; private final ConnectionRegistry connectionRegistry;
private final PluginManager pluginManager; private final PluginManager pluginManager;
private final SecureRandom random; private final SecureRandom random;
private final Map<TransportId, PollTask> tasks; private final Clock clock;
private final Lock lock;
private final Map<TransportId, PollTask> tasks; // Locking: lock
@Inject @Inject
Poller(@IoExecutor Executor ioExecutor, ScheduledExecutorService scheduler, Poller(@IoExecutor Executor ioExecutor, ScheduledExecutorService scheduler,
ConnectionManager connectionManager, ConnectionManager connectionManager,
ConnectionRegistry connectionRegistry, PluginManager pluginManager, ConnectionRegistry connectionRegistry, PluginManager pluginManager,
SecureRandom random) { SecureRandom random, Clock clock) {
this.ioExecutor = ioExecutor; this.ioExecutor = ioExecutor;
this.connectionManager = connectionManager; this.connectionManager = connectionManager;
this.connectionRegistry = connectionRegistry; this.connectionRegistry = connectionRegistry;
this.pluginManager = pluginManager; this.pluginManager = pluginManager;
this.random = random; this.random = random;
this.scheduler = scheduler; this.scheduler = scheduler;
tasks = new ConcurrentHashMap<TransportId, PollTask>(); this.clock = clock;
lock = new ReentrantLock();
tasks = new HashMap<TransportId, PollTask>();
} }
@Override @Override
@@ -65,12 +73,19 @@ class Poller implements EventListener {
} }
} else if (e instanceof ConnectionClosedEvent) { } else if (e instanceof ConnectionClosedEvent) {
ConnectionClosedEvent c = (ConnectionClosedEvent) e; ConnectionClosedEvent c = (ConnectionClosedEvent) e;
// Reschedule polling, the polling interval may have decreased
reschedule(c.getTransportId());
if (!c.isIncoming()) { if (!c.isIncoming()) {
// Connect to the disconnected contact // Connect to the disconnected contact
connectToContact(c.getContactId(), c.getTransportId()); connectToContact(c.getContactId(), c.getTransportId());
} }
} else if (e instanceof ConnectionOpenedEvent) {
ConnectionOpenedEvent c = (ConnectionOpenedEvent) e;
// Reschedule polling, the polling interval may have decreased
reschedule(c.getTransportId());
} else if (e instanceof TransportEnabledEvent) { } else if (e instanceof TransportEnabledEvent) {
TransportEnabledEvent t = (TransportEnabledEvent) e; TransportEnabledEvent t = (TransportEnabledEvent) e;
// Poll the newly enabled transport
pollNow(t.getTransportId()); pollNow(t.getTransportId());
} }
} }
@@ -118,18 +133,32 @@ class Poller implements EventListener {
}); });
} }
private void reschedule(TransportId t) {
Plugin p = pluginManager.getPlugin(t);
if (p.shouldPoll()) schedule(p, p.getPollingInterval(), false);
}
private void pollNow(TransportId t) { private void pollNow(TransportId t) {
Plugin p = pluginManager.getPlugin(t); Plugin p = pluginManager.getPlugin(t);
// Randomise next polling interval // Randomise next polling interval
if (p.shouldPoll()) schedule(p, 0, true); if (p.shouldPoll()) schedule(p, 0, true);
} }
private void schedule(Plugin p, int interval, boolean randomiseNext) { private void schedule(Plugin p, int delay, boolean randomiseNext) {
// Replace any previously scheduled task for this plugin // Replace any later scheduled task for this plugin
PollTask task = new PollTask(p, randomiseNext); long due = clock.currentTimeMillis() + delay;
PollTask replaced = tasks.put(p.getId(), task); lock.lock();
if (replaced != null) replaced.cancel(); try {
scheduler.schedule(task, interval, MILLISECONDS); TransportId t = p.getId();
PollTask scheduled = tasks.get(t);
if (scheduled == null || due < scheduled.due) {
PollTask task = new PollTask(p, due, randomiseNext);
tasks.put(t, task);
scheduler.schedule(task, delay, MILLISECONDS);
}
} finally {
lock.unlock();
}
} }
private void poll(final Plugin p) { private void poll(final Plugin p) {
@@ -146,27 +175,28 @@ class Poller implements EventListener {
private class PollTask implements Runnable { private class PollTask implements Runnable {
private final Plugin plugin; private final Plugin plugin;
private final long due;
private final boolean randomiseNext; private final boolean randomiseNext;
private volatile boolean cancelled = false; private PollTask(Plugin plugin, long due, boolean randomiseNext) {
private PollTask(Plugin plugin, boolean randomiseNext) {
this.plugin = plugin; this.plugin = plugin;
this.due = due;
this.randomiseNext = randomiseNext; this.randomiseNext = randomiseNext;
} }
private void cancel() {
cancelled = true;
}
@Override @Override
public void run() { public void run() {
if (cancelled) return; lock.lock();
tasks.remove(plugin.getId()); try {
int interval = plugin.getPollingInterval(); TransportId t = plugin.getId();
if (randomiseNext) if (tasks.get(t) != this) return; // Replaced by another task
interval = (int) (interval * random.nextDouble()); tasks.remove(t);
schedule(plugin, interval, false); } finally {
lock.unlock();
}
int delay = plugin.getPollingInterval();
if (randomiseNext) delay = (int) (delay * random.nextDouble());
schedule(plugin, delay, false);
poll(plugin); poll(plugin);
} }
} }

View File

@@ -6,6 +6,7 @@ import org.briarproject.RunAction;
import org.briarproject.api.TransportId; import org.briarproject.api.TransportId;
import org.briarproject.api.contact.ContactId; import org.briarproject.api.contact.ContactId;
import org.briarproject.api.event.ConnectionClosedEvent; import org.briarproject.api.event.ConnectionClosedEvent;
import org.briarproject.api.event.ConnectionOpenedEvent;
import org.briarproject.api.event.ContactStatusChangedEvent; import org.briarproject.api.event.ContactStatusChangedEvent;
import org.briarproject.api.event.TransportEnabledEvent; import org.briarproject.api.event.TransportEnabledEvent;
import org.briarproject.api.plugins.ConnectionManager; import org.briarproject.api.plugins.ConnectionManager;
@@ -16,6 +17,7 @@ import org.briarproject.api.plugins.TransportConnectionWriter;
import org.briarproject.api.plugins.duplex.DuplexPlugin; import org.briarproject.api.plugins.duplex.DuplexPlugin;
import org.briarproject.api.plugins.duplex.DuplexTransportConnection; import org.briarproject.api.plugins.duplex.DuplexTransportConnection;
import org.briarproject.api.plugins.simplex.SimplexPlugin; import org.briarproject.api.plugins.simplex.SimplexPlugin;
import org.briarproject.api.system.Clock;
import org.jmock.Expectations; import org.jmock.Expectations;
import org.jmock.Mockery; import org.jmock.Mockery;
import org.jmock.lib.legacy.ClassImposteriser; import org.jmock.lib.legacy.ClassImposteriser;
@@ -33,9 +35,11 @@ import static java.util.concurrent.TimeUnit.MILLISECONDS;
public class PollerTest extends BriarTestCase { public class PollerTest extends BriarTestCase {
private final ContactId contactId = new ContactId(234); private final ContactId contactId = new ContactId(234);
private final int pollingInterval = 60 * 1000;
private final long now = System.currentTimeMillis();
@Test @Test
public void testConnectToNewContact() throws Exception { public void testConnectOnContactStatusChanged() throws Exception {
Mockery context = new Mockery(); Mockery context = new Mockery();
context.setImposteriser(ClassImposteriser.INSTANCE); context.setImposteriser(ClassImposteriser.INSTANCE);
final Executor ioExecutor = new ImmediateExecutor(); final Executor ioExecutor = new ImmediateExecutor();
@@ -47,6 +51,7 @@ public class PollerTest extends BriarTestCase {
context.mock(ConnectionRegistry.class); context.mock(ConnectionRegistry.class);
final PluginManager pluginManager = context.mock(PluginManager.class); final PluginManager pluginManager = context.mock(PluginManager.class);
final SecureRandom random = context.mock(SecureRandom.class); final SecureRandom random = context.mock(SecureRandom.class);
final Clock clock = context.mock(Clock.class);
// Two simplex plugins: one supports polling, the other doesn't // Two simplex plugins: one supports polling, the other doesn't
final SimplexPlugin simplexPlugin = context.mock(SimplexPlugin.class); final SimplexPlugin simplexPlugin = context.mock(SimplexPlugin.class);
@@ -112,7 +117,7 @@ public class PollerTest extends BriarTestCase {
}}); }});
Poller p = new Poller(ioExecutor, scheduler, connectionManager, Poller p = new Poller(ioExecutor, scheduler, connectionManager,
connectionRegistry, pluginManager, random); connectionRegistry, pluginManager, random, clock);
p.eventOccurred(new ContactStatusChangedEvent(contactId, true)); p.eventOccurred(new ContactStatusChangedEvent(contactId, true));
@@ -120,7 +125,8 @@ public class PollerTest extends BriarTestCase {
} }
@Test @Test
public void testReconnectToDisconnectedContact() throws Exception { public void testRescheduleAndReconnectOnConnectionClosed()
throws Exception {
Mockery context = new Mockery(); Mockery context = new Mockery();
context.setImposteriser(ClassImposteriser.INSTANCE); context.setImposteriser(ClassImposteriser.INSTANCE);
final Executor ioExecutor = new ImmediateExecutor(); final Executor ioExecutor = new ImmediateExecutor();
@@ -132,6 +138,7 @@ public class PollerTest extends BriarTestCase {
context.mock(ConnectionRegistry.class); context.mock(ConnectionRegistry.class);
final PluginManager pluginManager = context.mock(PluginManager.class); final PluginManager pluginManager = context.mock(PluginManager.class);
final SecureRandom random = context.mock(SecureRandom.class); final SecureRandom random = context.mock(SecureRandom.class);
final Clock clock = context.mock(Clock.class);
final DuplexPlugin plugin = context.mock(DuplexPlugin.class); final DuplexPlugin plugin = context.mock(DuplexPlugin.class);
final TransportId transportId = new TransportId("id"); final TransportId transportId = new TransportId("id");
@@ -139,15 +146,30 @@ public class PollerTest extends BriarTestCase {
context.mock(DuplexTransportConnection.class); context.mock(DuplexTransportConnection.class);
context.checking(new Expectations() {{ context.checking(new Expectations() {{
allowing(plugin).getId();
will(returnValue(transportId));
// reschedule()
// Get the plugin // Get the plugin
oneOf(pluginManager).getPlugin(transportId); oneOf(pluginManager).getPlugin(transportId);
will(returnValue(plugin)); will(returnValue(plugin));
// The plugin supports polling // The plugin supports polling
oneOf(plugin).shouldPoll(); oneOf(plugin).shouldPoll();
will(returnValue(true)); will(returnValue(true));
// Get the plugin
oneOf(pluginManager).getPlugin(transportId);
will(returnValue(plugin));
// The plugin supports polling
oneOf(plugin).shouldPoll();
will(returnValue(true));
// Schedule the next poll
oneOf(plugin).getPollingInterval();
will(returnValue(pollingInterval));
oneOf(clock).currentTimeMillis();
will(returnValue(now));
oneOf(scheduler).schedule(with(any(Runnable.class)),
with((long) pollingInterval), with(MILLISECONDS));
// connectToContact()
// Check whether the contact is already connected // Check whether the contact is already connected
oneOf(plugin).getId();
will(returnValue(transportId));
oneOf(connectionRegistry).isConnected(contactId, transportId); oneOf(connectionRegistry).isConnected(contactId, transportId);
will(returnValue(false)); will(returnValue(false));
// Connect to the contact // Connect to the contact
@@ -159,7 +181,7 @@ public class PollerTest extends BriarTestCase {
}}); }});
Poller p = new Poller(ioExecutor, scheduler, connectionManager, Poller p = new Poller(ioExecutor, scheduler, connectionManager,
connectionRegistry, pluginManager, random); connectionRegistry, pluginManager, random, clock);
p.eventOccurred(new ConnectionClosedEvent(contactId, transportId, p.eventOccurred(new ConnectionClosedEvent(contactId, transportId,
false)); false));
@@ -167,8 +189,9 @@ public class PollerTest extends BriarTestCase {
context.assertIsSatisfied(); context.assertIsSatisfied();
} }
@Test @Test
public void testPollWhenTransportIsEnabled() throws Exception { public void testRescheduleOnConnectionOpened() throws Exception {
Mockery context = new Mockery(); Mockery context = new Mockery();
context.setImposteriser(ClassImposteriser.INSTANCE); context.setImposteriser(ClassImposteriser.INSTANCE);
final Executor ioExecutor = new ImmediateExecutor(); final Executor ioExecutor = new ImmediateExecutor();
@@ -180,10 +203,115 @@ public class PollerTest extends BriarTestCase {
context.mock(ConnectionRegistry.class); context.mock(ConnectionRegistry.class);
final PluginManager pluginManager = context.mock(PluginManager.class); final PluginManager pluginManager = context.mock(PluginManager.class);
final SecureRandom random = context.mock(SecureRandom.class); final SecureRandom random = context.mock(SecureRandom.class);
final Clock clock = context.mock(Clock.class);
final DuplexPlugin plugin = context.mock(DuplexPlugin.class);
final TransportId transportId = new TransportId("id");
context.checking(new Expectations() {{
allowing(plugin).getId();
will(returnValue(transportId));
// Get the plugin
oneOf(pluginManager).getPlugin(transportId);
will(returnValue(plugin));
// The plugin supports polling
oneOf(plugin).shouldPoll();
will(returnValue(true));
// Schedule the next poll
oneOf(plugin).getPollingInterval();
will(returnValue(pollingInterval));
oneOf(clock).currentTimeMillis();
will(returnValue(now));
oneOf(scheduler).schedule(with(any(Runnable.class)),
with((long) pollingInterval), with(MILLISECONDS));
}});
Poller p = new Poller(ioExecutor, scheduler, connectionManager,
connectionRegistry, pluginManager, random, clock);
p.eventOccurred(new ConnectionOpenedEvent(contactId, transportId,
false));
context.assertIsSatisfied();
}
@Test
public void testRescheduleDoesNotReplaceEarlierTask() throws Exception {
Mockery context = new Mockery();
context.setImposteriser(ClassImposteriser.INSTANCE);
final Executor ioExecutor = new ImmediateExecutor();
final ScheduledExecutorService scheduler =
context.mock(ScheduledExecutorService.class);
final ConnectionManager connectionManager =
context.mock(ConnectionManager.class);
final ConnectionRegistry connectionRegistry =
context.mock(ConnectionRegistry.class);
final PluginManager pluginManager = context.mock(PluginManager.class);
final SecureRandom random = context.mock(SecureRandom.class);
final Clock clock = context.mock(Clock.class);
final DuplexPlugin plugin = context.mock(DuplexPlugin.class);
final TransportId transportId = new TransportId("id");
context.checking(new Expectations() {{
allowing(plugin).getId();
will(returnValue(transportId));
// First event
// Get the plugin
oneOf(pluginManager).getPlugin(transportId);
will(returnValue(plugin));
// The plugin supports polling
oneOf(plugin).shouldPoll();
will(returnValue(true));
// Schedule the next poll
oneOf(plugin).getPollingInterval();
will(returnValue(pollingInterval));
oneOf(clock).currentTimeMillis();
will(returnValue(now));
oneOf(scheduler).schedule(with(any(Runnable.class)),
with((long) pollingInterval), with(MILLISECONDS));
// Second event
// Get the plugin
oneOf(pluginManager).getPlugin(transportId);
will(returnValue(plugin));
// The plugin supports polling
oneOf(plugin).shouldPoll();
will(returnValue(true));
// Don't replace the previously scheduled task, due earlier
oneOf(plugin).getPollingInterval();
will(returnValue(pollingInterval));
oneOf(clock).currentTimeMillis();
will(returnValue(now + 1));
}});
Poller p = new Poller(ioExecutor, scheduler, connectionManager,
connectionRegistry, pluginManager, random, clock);
p.eventOccurred(new ConnectionOpenedEvent(contactId, transportId,
false));
p.eventOccurred(new ConnectionOpenedEvent(contactId, transportId,
false));
context.assertIsSatisfied();
}
@Test
public void testPollOnTransportEnabled() throws Exception {
Mockery context = new Mockery();
context.setImposteriser(ClassImposteriser.INSTANCE);
final Executor ioExecutor = new ImmediateExecutor();
final ScheduledExecutorService scheduler =
context.mock(ScheduledExecutorService.class);
final ConnectionManager connectionManager =
context.mock(ConnectionManager.class);
final ConnectionRegistry connectionRegistry =
context.mock(ConnectionRegistry.class);
final PluginManager pluginManager = context.mock(PluginManager.class);
final SecureRandom random = context.mock(SecureRandom.class);
final Clock clock = context.mock(Clock.class);
final Plugin plugin = context.mock(Plugin.class); final Plugin plugin = context.mock(Plugin.class);
final TransportId transportId = new TransportId("id"); final TransportId transportId = new TransportId("id");
final int pollingInterval = 60 * 1000;
final List<ContactId> connected = Collections.singletonList(contactId); final List<ContactId> connected = Collections.singletonList(contactId);
context.checking(new Expectations() {{ context.checking(new Expectations() {{
@@ -196,14 +324,18 @@ public class PollerTest extends BriarTestCase {
oneOf(plugin).shouldPoll(); oneOf(plugin).shouldPoll();
will(returnValue(true)); will(returnValue(true));
// Schedule a polling task immediately // Schedule a polling task immediately
oneOf(clock).currentTimeMillis();
will(returnValue(now));
oneOf(scheduler).schedule(with(any(Runnable.class)), with(0L), oneOf(scheduler).schedule(with(any(Runnable.class)), with(0L),
with(MILLISECONDS)); with(MILLISECONDS));
will(new RunAction()); will(new RunAction());
// Run the polling task // Running the polling task schedules the next polling task
oneOf(plugin).getPollingInterval(); oneOf(plugin).getPollingInterval();
will(returnValue(pollingInterval)); will(returnValue(pollingInterval));
oneOf(random).nextDouble(); oneOf(random).nextDouble();
will(returnValue(0.5)); will(returnValue(0.5));
oneOf(clock).currentTimeMillis();
will(returnValue(now));
oneOf(scheduler).schedule(with(any(Runnable.class)), oneOf(scheduler).schedule(with(any(Runnable.class)),
with((long) (pollingInterval * 0.5)), with(MILLISECONDS)); with((long) (pollingInterval * 0.5)), with(MILLISECONDS));
// Poll the plugin // Poll the plugin
@@ -213,7 +345,7 @@ public class PollerTest extends BriarTestCase {
}}); }});
Poller p = new Poller(ioExecutor, scheduler, connectionManager, Poller p = new Poller(ioExecutor, scheduler, connectionManager,
connectionRegistry, pluginManager, random); connectionRegistry, pluginManager, random, clock);
p.eventOccurred(new TransportEnabledEvent(transportId)); p.eventOccurred(new TransportEnabledEvent(transportId));