Merge branch 'start-plugins-async' into 'master'

Start plugins asynchronously

This prevents other services from getting stuck behind the plugin manager while the Tor plugin is starting, which takes several seconds. The plugin manager waits for each plugin to start before stopping it.

I've also added some canaries to plugins and services to ensure instances aren't started more than once.

See merge request !181
This commit is contained in:
akwizgran
2016-05-11 14:46:55 +00:00
16 changed files with 252 additions and 90 deletions

View File

@@ -42,6 +42,7 @@ import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Logger;
import javax.inject.Inject;
@@ -81,10 +82,10 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
private final Context appContext;
// The following must only be accessed on the main UI thread
private final Map<GroupId, Integer> contactCounts =
new HashMap<GroupId, Integer>();
private final Map<GroupId, Integer> forumCounts =
new HashMap<GroupId, Integer>();
private final Map<GroupId, Integer> contactCounts = new HashMap<>();
private final Map<GroupId, Integer> forumCounts = new HashMap<>();
private final AtomicBoolean used = new AtomicBoolean(false);
private int contactTotal = 0, forumTotal = 0;
private int nextRequestId = 0;
private GroupId visibleGroup = null;
@@ -106,6 +107,7 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
@Override
public void startService() throws ServiceException {
if (used.getAndSet(true)) throw new IllegalStateException();
try {
settings = settingsManager.getSettings(SETTINGS_NAMESPACE);
} catch (DbException e) {
@@ -116,6 +118,7 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
@Override
public void stopService() throws ServiceException {
Future<Void> f = androidExecutor.submit(new Callable<Void>() {
@Override
public Void call() {
clearPrivateMessageNotification();
clearForumPostNotification();
@@ -125,9 +128,7 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
});
try {
f.get();
} catch (InterruptedException e) {
throw new ServiceException(e);
} catch (ExecutionException e) {
} catch (InterruptedException | ExecutionException e) {
throw new ServiceException(e);
}
}
@@ -150,6 +151,7 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
nm.cancel(INTRODUCTION_SUCCESS_NOTIFICATION_ID);
}
@Override
public void eventOccurred(Event e) {
if (e instanceof SettingsUpdatedEvent) {
SettingsUpdatedEvent s = (SettingsUpdatedEvent) e;
@@ -180,6 +182,7 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
private void loadSettings() {
dbExecutor.execute(new Runnable() {
@Override
public void run() {
try {
settings = settingsManager.getSettings(SETTINGS_NAMESPACE);
@@ -191,8 +194,10 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
});
}
@Override
public void showPrivateMessageNotification(final GroupId g) {
androidExecutor.execute(new Runnable() {
@Override
public void run() {
Integer count = contactCounts.get(g);
if (count == null) contactCounts.put(g, 1);
@@ -204,8 +209,10 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
});
}
@Override
public void clearPrivateMessageNotification(final GroupId g) {
androidExecutor.execute(new Runnable() {
@Override
public void run() {
Integer count = contactCounts.remove(g);
if (count == null) return; // Already cleared
@@ -275,8 +282,10 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
return defaults;
}
@Override
public void showForumPostNotification(final GroupId g) {
androidExecutor.execute(new Runnable() {
@Override
public void run() {
Integer count = forumCounts.get(g);
if (count == null) forumCounts.put(g, 1);
@@ -288,8 +297,10 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
});
}
@Override
public void clearForumPostNotification(final GroupId g) {
androidExecutor.execute(new Runnable() {
@Override
public void run() {
Integer count = forumCounts.remove(g);
if (count == null) return; // Already cleared
@@ -347,16 +358,20 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
}
}
@Override
public void blockNotification(final GroupId g) {
androidExecutor.execute(new Runnable() {
@Override
public void run() {
visibleGroup = g;
}
});
}
@Override
public void unblockNotification(final GroupId g) {
androidExecutor.execute(new Runnable() {
@Override
public void run() {
if (g.equals(visibleGroup)) visibleGroup = null;
}
@@ -365,6 +380,7 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
private void showNotificationForPrivateConversation(final ContactId c) {
androidExecutor.execute(new Runnable() {
@Override
public void run() {
try {
GroupId group = messagingManager.getConversationId(c);
@@ -379,6 +395,7 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
private void showIntroductionSucceededNotification(final Contact c) {
androidExecutor.execute(new Runnable() {
@Override
public void run() {
NotificationCompat.Builder b =
new NotificationCompat.Builder(appContext);

View File

@@ -9,9 +9,9 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import org.briarproject.android.api.AndroidExecutor;
import org.briarproject.android.util.AndroidUtils;
import org.briarproject.api.TransportId;
import org.briarproject.android.api.AndroidExecutor;
import org.briarproject.api.contact.ContactId;
import org.briarproject.api.crypto.PseudoRandom;
import org.briarproject.api.keyagreement.KeyAgreementConnection;
@@ -41,6 +41,7 @@ import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorCompletionService;
import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Logger;
import static android.bluetooth.BluetoothAdapter.ACTION_SCAN_MODE_CHANGED;
@@ -80,6 +81,7 @@ class DroidtoothPlugin implements DuplexPlugin {
private final Backoff backoff;
private final DuplexPluginCallback callback;
private final int maxLatency;
private final AtomicBoolean used = new AtomicBoolean(false);
private volatile boolean running = false;
private volatile boolean wasEnabledByUs = false;
@@ -101,24 +103,30 @@ class DroidtoothPlugin implements DuplexPlugin {
this.maxLatency = maxLatency;
}
@Override
public TransportId getId() {
return ID;
}
@Override
public int getMaxLatency() {
return maxLatency;
}
@Override
public int getMaxIdleTime() {
// Bluetooth detects dead connections so we don't need keepalives
return Integer.MAX_VALUE;
}
@Override
public boolean start() throws IOException {
if (used.getAndSet(true)) throw new IllegalStateException();
// BluetoothAdapter.getDefaultAdapter() must be called on a thread
// with a message queue, so submit it to the AndroidExecutor
try {
adapter = androidExecutor.submit(new Callable<BluetoothAdapter>() {
@Override
public BluetoothAdapter call() throws Exception {
return BluetoothAdapter.getDefaultAdapter();
}
@@ -158,6 +166,7 @@ class DroidtoothPlugin implements DuplexPlugin {
private void bind() {
ioExecutor.execute(new Runnable() {
@Override
public void run() {
if (!isRunning()) return;
String address = AndroidUtils.getBluetoothAddress(appContext,
@@ -238,6 +247,7 @@ class DroidtoothPlugin implements DuplexPlugin {
return new DroidtoothTransportConnection(this, s);
}
@Override
public void stop() {
running = false;
if (receiver != null) appContext.unregisterReceiver(receiver);
@@ -249,18 +259,22 @@ class DroidtoothPlugin implements DuplexPlugin {
}
}
@Override
public boolean isRunning() {
return running && adapter.isEnabled();
}
@Override
public boolean shouldPoll() {
return true;
}
@Override
public int getPollingInterval() {
return backoff.getPollingInterval();
}
@Override
public void poll(Collection<ContactId> connected) {
if (!isRunning()) return;
backoff.increment();
@@ -275,6 +289,7 @@ class DroidtoothPlugin implements DuplexPlugin {
final String uuid = e.getValue().get(PROP_UUID);
if (StringUtils.isNullOrEmpty(uuid)) continue;
ioExecutor.execute(new Runnable() {
@Override
public void run() {
if (!running) return;
BluetoothSocket s = connect(address, uuid);
@@ -327,6 +342,7 @@ class DroidtoothPlugin implements DuplexPlugin {
}
}
@Override
public DuplexTransportConnection createConnection(ContactId c) {
if (!isRunning()) return null;
TransportProperties p = callback.getRemoteProperties().get(c);
@@ -340,10 +356,12 @@ class DroidtoothPlugin implements DuplexPlugin {
return new DroidtoothTransportConnection(this, s);
}
@Override
public boolean supportsInvitations() {
return true;
}
@Override
public DuplexTransportConnection createInvitationConnection(PseudoRandom r,
long timeout, boolean alice) {
if (!isRunning()) return null;
@@ -361,9 +379,8 @@ class DroidtoothPlugin implements DuplexPlugin {
}
// Create the background tasks
CompletionService<BluetoothSocket> complete =
new ExecutorCompletionService<BluetoothSocket>(ioExecutor);
List<Future<BluetoothSocket>> futures =
new ArrayList<Future<BluetoothSocket>>();
new ExecutorCompletionService<>(ioExecutor);
List<Future<BluetoothSocket>> futures = new ArrayList<>();
if (alice) {
// Return the first connected socket
futures.add(complete.submit(new ListeningTask(ss)));
@@ -398,6 +415,7 @@ class DroidtoothPlugin implements DuplexPlugin {
private void closeSockets(final List<Future<BluetoothSocket>> futures,
final BluetoothSocket chosen) {
ioExecutor.execute(new Runnable() {
@Override
public void run() {
for (Future<BluetoothSocket> f : futures) {
try {
@@ -413,9 +431,7 @@ class DroidtoothPlugin implements DuplexPlugin {
} catch (InterruptedException e) {
LOG.info("Interrupted while closing sockets");
return;
} catch (ExecutionException e) {
if (LOG.isLoggable(INFO)) LOG.info(e.toString());
} catch (IOException e) {
} catch (ExecutionException | IOException e) {
if (LOG.isLoggable(INFO)) LOG.info(e.toString());
}
}
@@ -423,14 +439,15 @@ class DroidtoothPlugin implements DuplexPlugin {
});
}
@Override
public boolean supportsKeyAgreement() {
return true;
}
public KeyAgreementListener createKeyAgreementListener(
byte[] localCommitment) {
@Override
public KeyAgreementListener createKeyAgreementListener(byte[] commitment) {
// No truncation necessary because COMMIT_LENGTH = 16
UUID uuid = UUID.nameUUIDFromBytes(localCommitment);
UUID uuid = UUID.nameUUIDFromBytes(commitment);
if (LOG.isLoggable(INFO)) LOG.info("Key agreement UUID " + uuid);
// Bind a server socket for receiving invitation connections
BluetoothServerSocket ss;
@@ -448,8 +465,9 @@ class DroidtoothPlugin implements DuplexPlugin {
return new BluetoothKeyAgreementListener(d, ss);
}
@Override
public DuplexTransportConnection createKeyAgreementConnection(
byte[] remoteCommitment, TransportDescriptor d, long timeout) {
byte[] commitment, TransportDescriptor d, long timeout) {
if (!isRunning()) return null;
if (!ID.equals(d.getIdentifier())) return null;
TransportProperties p = d.getProperties();
@@ -457,7 +475,7 @@ class DroidtoothPlugin implements DuplexPlugin {
String address = p.get(PROP_ADDRESS);
if (StringUtils.isNullOrEmpty(address)) return null;
// No truncation necessary because COMMIT_LENGTH = 16
UUID uuid = UUID.nameUUIDFromBytes(remoteCommitment);
UUID uuid = UUID.nameUUIDFromBytes(commitment);
if (LOG.isLoggable(INFO))
LOG.info("Connecting to key agreement UUID " + uuid);
BluetoothSocket s = connect(address, uuid.toString());
@@ -533,7 +551,7 @@ class DroidtoothPlugin implements DuplexPlugin {
private static class DiscoveryReceiver extends BroadcastReceiver {
private final CountDownLatch finished = new CountDownLatch(1);
private final List<String> addresses = new ArrayList<String>();
private final List<String> addresses = new ArrayList<>();
@Override
public void onReceive(Context ctx, Intent intent) {

View File

@@ -35,6 +35,7 @@ class AndroidLanTcpPlugin extends LanTcpPlugin {
@Override
public boolean start() {
if (used.getAndSet(true)) throw new IllegalStateException();
running = true;
// Register to receive network status events
networkStateReceiver = new NetworkStateReceiver();

View File

@@ -51,6 +51,7 @@ import java.util.Map;
import java.util.Scanner;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Logger;
import java.util.regex.Pattern;
import java.util.zip.ZipInputStream;
@@ -94,6 +95,7 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
private final File torDirectory, torFile, geoIpFile, configFile;
private final File doneFile, cookieFile;
private final PowerManager.WakeLock wakeLock;
private final AtomicBoolean used = new AtomicBoolean(false);
private volatile boolean running = false;
private volatile ServerSocket socket = null;
@@ -130,19 +132,24 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
wakeLock.setReferenceCounted(false);
}
@Override
public TransportId getId() {
return ID;
}
@Override
public int getMaxLatency() {
return maxLatency;
}
@Override
public int getMaxIdleTime() {
return maxIdleTime;
}
@Override
public boolean start() throws IOException {
if (used.getAndSet(true)) throw new IllegalStateException();
// Try to connect to an existing Tor process if there is one
boolean startProcess = false;
try {
@@ -210,13 +217,13 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
// Now we should be able to connect to the new process
controlSocket = new Socket("127.0.0.1", CONTROL_PORT);
}
running = true;
// Open a control connection and authenticate using the cookie file
controlConnection = new TorControlConnection(controlSocket);
controlConnection.authenticate(read(cookieFile));
// Tell Tor to exit when the control connection is closed
controlConnection.takeOwnership();
controlConnection.resetConf(Collections.singletonList(OWNER));
running = true;
// Register to receive events from the Tor process
controlConnection.setEventHandler(this);
controlConnection.setEvents(Arrays.asList(EVENTS));
@@ -226,7 +233,6 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
if (phase != null && phase.contains("PROGRESS=100")) {
LOG.info("Tor has already bootstrapped");
connectionStatus.setBootstrapped();
sendDevReports();
}
}
// Register to receive network status events
@@ -369,6 +375,7 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
private void bind() {
ioExecutor.execute(new Runnable() {
@Override
public void run() {
// If there's already a port number stored in config, reuse it
String portString = callback.getSettings().get("port");
@@ -398,6 +405,7 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
callback.mergeSettings(s);
// Create a hidden service if necessary
ioExecutor.execute(new Runnable() {
@Override
public void run() {
publishHiddenService(localPort);
}
@@ -486,6 +494,7 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
}
}
@Override
public void stop() throws IOException {
running = false;
tryToClose(socket);
@@ -508,18 +517,22 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
wakeLock.release();
}
@Override
public boolean isRunning() {
return running && connectionStatus.isConnected();
}
@Override
public boolean shouldPoll() {
return true;
}
@Override
public int getPollingInterval() {
return backoff.getPollingInterval();
}
@Override
public void poll(Collection<ContactId> connected) {
if (!isRunning()) return;
backoff.increment();
@@ -530,6 +543,7 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
private void connectAndCallBack(final ContactId c) {
ioExecutor.execute(new Runnable() {
@Override
public void run() {
DuplexTransportConnection d = createConnection(c);
if (d != null) {
@@ -540,6 +554,7 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
});
}
@Override
public DuplexTransportConnection createConnection(ContactId c) {
if (!isRunning()) return null;
TransportProperties p = callback.getRemoteProperties().get(c);
@@ -566,61 +581,77 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
}
}
@Override
public boolean supportsInvitations() {
return false;
}
@Override
public DuplexTransportConnection createInvitationConnection(PseudoRandom r,
long timeout, boolean alice) {
throw new UnsupportedOperationException();
}
@Override
public boolean supportsKeyAgreement() {
return false;
}
public KeyAgreementListener createKeyAgreementListener(
byte[] commitment) {
@Override
public KeyAgreementListener createKeyAgreementListener(byte[] commitment) {
throw new UnsupportedOperationException();
}
@Override
public DuplexTransportConnection createKeyAgreementConnection(
byte[] commitment, TransportDescriptor d, long timeout) {
throw new UnsupportedOperationException();
}
@Override
public void circuitStatus(String status, String id, String path) {
if (status.equals("BUILT") &&
connectionStatus.getAndSetCircuitBuilt()) {
LOG.info("First circuit built");
backoff.reset();
if (isRunning()) callback.transportEnabled();
if (isRunning()) {
sendDevReports();
callback.transportEnabled();
}
}
}
@Override
public void streamStatus(String status, String id, String target) {
}
@Override
public void orConnStatus(String status, String orName) {
if (LOG.isLoggable(INFO)) LOG.info("OR connection " + status);
}
@Override
public void bandwidthUsed(long read, long written) {
}
@Override
public void newDescriptors(List<String> orList) {
}
@Override
public void message(String severity, String msg) {
if (LOG.isLoggable(INFO)) LOG.info(severity + " " + msg);
if (severity.equals("NOTICE") && msg.startsWith("Bootstrapped 100%")) {
connectionStatus.setBootstrapped();
sendDevReports();
backoff.reset();
if (isRunning()) callback.transportEnabled();
if (isRunning()) {
sendDevReports();
callback.transportEnabled();
}
}
}
@Override
public void unrecognized(String type, String msg) {
if (type.equals("HS_DESC") && msg.startsWith("UPLOADED"))
LOG.info("Descriptor uploaded");
@@ -642,6 +673,7 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
}
}
@Override
public void eventOccurred(Event e) {
if (e instanceof SettingsUpdatedEvent) {
if (((SettingsUpdatedEvent) e).getNamespace().equals("tor")) {
@@ -653,13 +685,14 @@ class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
private void updateConnectionStatus() {
ioExecutor.execute(new Runnable() {
@Override
public void run() {
if (!running) return;
Object o = appContext.getSystemService(CONNECTIVITY_SERVICE);
ConnectivityManager cm = (ConnectivityManager) o;
NetworkInfo net = cm.getActiveNetworkInfo();
boolean online = net != null && net.isConnected();
boolean online = net != null && net.isConnected();
boolean wifi = online && net.getType() == TYPE_WIFI;
String country = locationUtils.getCurrentCountry();
boolean blocked = TorNetworkMetadata.isTorProbablyBlocked(

View File

@@ -2,7 +2,6 @@ package org.briarproject.api.reporting;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
/**
* A task for reporting back to the developers.

View File

@@ -53,20 +53,20 @@ class LifecycleManagerImpl implements LifecycleManager {
@Override
public void registerService(Service s) {
if (LOG.isLoggable(INFO))
LOG.info("Registering service " + s.getClass().getName());
LOG.info("Registering service " + s.getClass().getSimpleName());
services.add(s);
}
@Override
public void registerClient(Client c) {
if (LOG.isLoggable(INFO))
LOG.info("Registering client " + c.getClass().getName());
LOG.info("Registering client " + c.getClass().getSimpleName());
clients.add(c);
}
@Override
public void registerForShutdown(ExecutorService e) {
LOG.info("Registering executor");
LOG.info("Registering executor " + e.getClass().getSimpleName());
executors.add(e);
}
@@ -94,7 +94,8 @@ class LifecycleManagerImpl implements LifecycleManager {
c.createLocalState(txn);
duration = System.currentTimeMillis() - start;
if (LOG.isLoggable(INFO)) {
LOG.info("Starting client " + c.getClass().getName()
LOG.info("Starting client "
+ c.getClass().getSimpleName()
+ " took " + duration + " ms");
}
}
@@ -107,7 +108,7 @@ class LifecycleManagerImpl implements LifecycleManager {
s.startService();
duration = System.currentTimeMillis() - start;
if (LOG.isLoggable(INFO)) {
LOG.info("Starting service " + s.getClass().getName()
LOG.info("Starting service " + s.getClass().getSimpleName()
+ " took " + duration + " ms");
}
}
@@ -140,13 +141,17 @@ class LifecycleManagerImpl implements LifecycleManager {
s.stopService();
long duration = System.currentTimeMillis() - start;
if (LOG.isLoggable(INFO)) {
LOG.info("Stopping service " + s.getClass().getName()
LOG.info("Stopping service " + s.getClass().getSimpleName()
+ " took " + duration + " ms");
}
}
for (ExecutorService e : executors) e.shutdownNow();
if (LOG.isLoggable(INFO))
LOG.info(executors.size() + " executors shut down");
for (ExecutorService e : executors) {
if (LOG.isLoggable(INFO)) {
LOG.info("Stopping executor "
+ e.getClass().getSimpleName());
}
e.shutdownNow();
}
long start = System.currentTimeMillis();
db.close();
long duration = System.currentTimeMillis() - start;

View File

@@ -39,6 +39,7 @@ import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Logger;
import javax.inject.Inject;
@@ -61,6 +62,8 @@ class PluginManagerImpl implements PluginManager, Service {
private final Map<TransportId, Plugin> plugins;
private final List<SimplexPlugin> simplexPlugins;
private final List<DuplexPlugin> duplexPlugins;
private final Map<TransportId, CountDownLatch> startLatches;
private final AtomicBoolean used = new AtomicBoolean(false);
@Inject
PluginManagerImpl(@IoExecutor Executor ioExecutor, EventBus eventBus,
@@ -78,68 +81,64 @@ class PluginManagerImpl implements PluginManager, Service {
plugins = new ConcurrentHashMap<TransportId, Plugin>();
simplexPlugins = new CopyOnWriteArrayList<SimplexPlugin>();
duplexPlugins = new CopyOnWriteArrayList<DuplexPlugin>();
startLatches = new ConcurrentHashMap<TransportId, CountDownLatch>();
}
@Override
public void startService() throws ServiceException {
Collection<SimplexPluginFactory> simplexFactories =
pluginConfig.getSimplexFactories();
Collection<DuplexPluginFactory> duplexFactories =
pluginConfig.getDuplexFactories();
int numPlugins = simplexFactories.size() + duplexFactories.size();
CountDownLatch latch = new CountDownLatch(numPlugins);
// Instantiate and start the simplex plugins
if (used.getAndSet(true)) throw new IllegalStateException();
// Instantiate the simplex plugins and start them asynchronously
LOG.info("Starting simplex plugins");
for (SimplexPluginFactory f : simplexFactories) {
for (SimplexPluginFactory f : pluginConfig.getSimplexFactories()) {
TransportId t = f.getId();
SimplexPlugin s = f.createPlugin(new SimplexCallback(t));
if (s == null) {
if (LOG.isLoggable(WARNING))
LOG.warning("Could not create plugin for " + t);
latch.countDown();
} else {
plugins.put(t, s);
simplexPlugins.add(s);
ioExecutor.execute(new PluginStarter(s, latch));
CountDownLatch startLatch = new CountDownLatch(1);
startLatches.put(t, startLatch);
ioExecutor.execute(new PluginStarter(s, startLatch));
}
}
// Instantiate and start the duplex plugins
// Instantiate the duplex plugins and start them asynchronously
LOG.info("Starting duplex plugins");
for (DuplexPluginFactory f : duplexFactories) {
for (DuplexPluginFactory f : pluginConfig.getDuplexFactories()) {
TransportId t = f.getId();
DuplexPlugin d = f.createPlugin(new DuplexCallback(t));
if (d == null) {
if (LOG.isLoggable(WARNING))
LOG.warning("Could not create plugin for " + t);
latch.countDown();
} else {
plugins.put(t, d);
duplexPlugins.add(d);
ioExecutor.execute(new PluginStarter(d, latch));
CountDownLatch startLatch = new CountDownLatch(1);
startLatches.put(t, startLatch);
ioExecutor.execute(new PluginStarter(d, startLatch));
}
}
// Wait for all the plugins to start
try {
latch.await();
} catch (InterruptedException e) {
throw new ServiceException(e);
}
}
@Override
public void stopService() throws ServiceException {
CountDownLatch latch = new CountDownLatch(plugins.size());
CountDownLatch stopLatch = new CountDownLatch(plugins.size());
// Stop the simplex plugins
LOG.info("Stopping simplex plugins");
for (SimplexPlugin plugin : simplexPlugins)
ioExecutor.execute(new PluginStopper(plugin, latch));
for (SimplexPlugin s : simplexPlugins) {
CountDownLatch startLatch = startLatches.get(s.getId());
ioExecutor.execute(new PluginStopper(s, startLatch, stopLatch));
}
// Stop the duplex plugins
LOG.info("Stopping duplex plugins");
for (DuplexPlugin plugin : duplexPlugins)
ioExecutor.execute(new PluginStopper(plugin, latch));
for (DuplexPlugin d : duplexPlugins) {
CountDownLatch startLatch = startLatches.get(d.getId());
ioExecutor.execute(new PluginStopper(d, startLatch, stopLatch));
}
// Wait for all the plugins to stop
try {
latch.await();
stopLatch.await();
} catch (InterruptedException e) {
throw new ServiceException(e);
}
@@ -179,11 +178,11 @@ class PluginManagerImpl implements PluginManager, Service {
private class PluginStarter implements Runnable {
private final Plugin plugin;
private final CountDownLatch latch;
private final CountDownLatch startLatch;
private PluginStarter(Plugin plugin, CountDownLatch latch) {
private PluginStarter(Plugin plugin, CountDownLatch startLatch) {
this.plugin = plugin;
this.latch = latch;
this.startLatch = startLatch;
}
@Override
@@ -209,7 +208,7 @@ class PluginManagerImpl implements PluginManager, Service {
LOG.log(WARNING, e.toString(), e);
}
} finally {
latch.countDown();
startLatch.countDown();
}
}
}
@@ -217,16 +216,21 @@ class PluginManagerImpl implements PluginManager, Service {
private class PluginStopper implements Runnable {
private final Plugin plugin;
private final CountDownLatch latch;
private final CountDownLatch startLatch, stopLatch;
private PluginStopper(Plugin plugin, CountDownLatch latch) {
private PluginStopper(Plugin plugin, CountDownLatch startLatch,
CountDownLatch stopLatch) {
this.plugin = plugin;
this.latch = latch;
this.startLatch = startLatch;
this.stopLatch = stopLatch;
}
@Override
public void run() {
try {
// Wait for the plugin to finish starting
startLatch.await();
// Stop the plugin
long start = System.currentTimeMillis();
plugin.stop();
long duration = System.currentTimeMillis() - start;
@@ -234,10 +238,13 @@ class PluginManagerImpl implements PluginManager, Service {
LOG.info("Stopping plugin " + plugin.getId()
+ " took " + duration + " ms");
}
} catch (InterruptedException e) {
LOG.warning("Interrupted while waiting for plugin to start");
// This task runs on an executor, so don't reset the interrupt
} catch (IOException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
} finally {
latch.countDown();
stopLatch.countDown();
}
}
}

View File

@@ -14,6 +14,7 @@ import java.io.OutputStream;
import java.util.Collection;
import java.util.Locale;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Logger;
import static java.util.logging.Level.WARNING;
@@ -27,6 +28,7 @@ public abstract class FilePlugin implements SimplexPlugin {
protected final Executor ioExecutor;
protected final SimplexPluginCallback callback;
protected final int maxLatency;
protected final AtomicBoolean used = new AtomicBoolean(false);
protected volatile boolean running = false;
@@ -42,22 +44,27 @@ public abstract class FilePlugin implements SimplexPlugin {
this.maxLatency = maxLatency;
}
@Override
public int getMaxLatency() {
return maxLatency;
}
@Override
public int getMaxIdleTime() {
return Integer.MAX_VALUE; // We don't need keepalives
}
@Override
public boolean isRunning() {
return running;
}
@Override
public TransportConnectionReader createReader(ContactId c) {
return null;
}
@Override
public TransportConnectionWriter createWriter(ContactId c) {
if (!running) return null;
return createWriter(createConnectionFilename());
@@ -105,6 +112,7 @@ public abstract class FilePlugin implements SimplexPlugin {
this.file = file;
}
@Override
public void run() {
if (isPossibleConnectionFilename(file.getName())) {
try {

View File

@@ -31,6 +31,7 @@ class LanTcpPlugin extends TcpPlugin {
super(ioExecutor, backoff, callback, maxLatency, maxIdleTime);
}
@Override
public TransportId getId() {
return ID;
}

View File

@@ -24,6 +24,7 @@ import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Logger;
import java.util.regex.Pattern;
@@ -41,6 +42,7 @@ abstract class TcpPlugin implements DuplexPlugin {
protected final Backoff backoff;
protected final DuplexPluginCallback callback;
protected final int maxLatency, maxIdleTime, socketTimeout;
protected final AtomicBoolean used = new AtomicBoolean(false);
protected volatile boolean running = false;
protected volatile ServerSocket socket = null;
@@ -81,15 +83,19 @@ abstract class TcpPlugin implements DuplexPlugin {
else socketTimeout = maxIdleTime * 2;
}
@Override
public int getMaxLatency() {
return maxLatency;
}
@Override
public int getMaxIdleTime() {
return maxIdleTime;
}
@Override
public boolean start() {
if (used.getAndSet(true)) throw new IllegalStateException();
running = true;
bind();
return true;
@@ -97,6 +103,7 @@ abstract class TcpPlugin implements DuplexPlugin {
protected void bind() {
ioExecutor.execute(new Runnable() {
@Override
public void run() {
if (!running) return;
ServerSocket ss = null;
@@ -166,23 +173,28 @@ abstract class TcpPlugin implements DuplexPlugin {
}
}
@Override
public void stop() {
running = false;
tryToClose(socket);
}
@Override
public boolean isRunning() {
return running && socket != null && !socket.isClosed();
}
@Override
public boolean shouldPoll() {
return true;
}
@Override
public int getPollingInterval() {
return backoff.getPollingInterval();
}
@Override
public void poll(Collection<ContactId> connected) {
if (!isRunning()) return;
backoff.increment();
@@ -193,6 +205,7 @@ abstract class TcpPlugin implements DuplexPlugin {
private void connectAndCallBack(final ContactId c) {
ioExecutor.execute(new Runnable() {
@Override
public void run() {
DuplexTransportConnection d = createConnection(c);
if (d != null) {
@@ -203,6 +216,7 @@ abstract class TcpPlugin implements DuplexPlugin {
});
}
@Override
public DuplexTransportConnection createConnection(ContactId c) {
if (!isRunning()) return null;
for (InetSocketAddress remote : getRemoteSocketAddresses(c)) {
@@ -250,24 +264,28 @@ abstract class TcpPlugin implements DuplexPlugin {
}
}
@Override
public boolean supportsInvitations() {
return false;
}
@Override
public DuplexTransportConnection createInvitationConnection(PseudoRandom r,
long timeout, boolean alice) {
throw new UnsupportedOperationException();
}
@Override
public boolean supportsKeyAgreement() {
return false;
}
public KeyAgreementListener createKeyAgreementListener(
byte[] commitment) {
@Override
public KeyAgreementListener createKeyAgreementListener(byte[] commitment) {
throw new UnsupportedOperationException();
}
@Override
public DuplexTransportConnection createKeyAgreementConnection(
byte[] commitment, TransportDescriptor d, long timeout) {
throw new UnsupportedOperationException();

View File

@@ -31,6 +31,7 @@ class WanTcpPlugin extends TcpPlugin {
this.portMapper = portMapper;
}
@Override
public TransportId getId() {
return ID;
}

View File

@@ -26,6 +26,7 @@ import java.util.Map;
import java.util.Queue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Logger;
import javax.inject.Inject;
@@ -44,6 +45,7 @@ class ValidationManagerImpl implements ValidationManager, Service,
private final Executor cryptoExecutor;
private final Map<ClientId, MessageValidator> validators;
private final Map<ClientId, IncomingMessageHook> hooks;
private final AtomicBoolean used = new AtomicBoolean(false);
@Inject
ValidationManagerImpl(DatabaseComponent db,
@@ -58,6 +60,7 @@ class ValidationManagerImpl implements ValidationManager, Service,
@Override
public void startService() {
if (used.getAndSet(true)) throw new IllegalStateException();
for (ClientId c : validators.keySet()) getMessagesToValidate(c);
}
@@ -78,6 +81,7 @@ class ValidationManagerImpl implements ValidationManager, Service,
private void getMessagesToValidate(final ClientId c) {
dbExecutor.execute(new Runnable() {
@Override
public void run() {
try {
Queue<MessageId> unvalidated = new LinkedList<MessageId>();
@@ -100,6 +104,7 @@ class ValidationManagerImpl implements ValidationManager, Service,
private void validateNextMessage(final Queue<MessageId> unvalidated) {
if (unvalidated.isEmpty()) return;
dbExecutor.execute(new Runnable() {
@Override
public void run() {
try {
Message m = null;
@@ -141,6 +146,7 @@ class ValidationManagerImpl implements ValidationManager, Service,
private void validateMessage(final Message m, final Group g) {
cryptoExecutor.execute(new Runnable() {
@Override
public void run() {
MessageValidator v = validators.get(g.getClientId());
if (v == null) {
@@ -156,6 +162,7 @@ class ValidationManagerImpl implements ValidationManager, Service,
private void storeValidationResult(final Message m, final ClientId c,
final Metadata meta) {
dbExecutor.execute(new Runnable() {
@Override
public void run() {
try {
Transaction txn = db.startTransaction(false);
@@ -193,6 +200,7 @@ class ValidationManagerImpl implements ValidationManager, Service,
private void loadGroupAndValidate(final Message m) {
dbExecutor.execute(new Runnable() {
@Override
public void run() {
try {
Group g;

View File

@@ -28,6 +28,7 @@ import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Logger;
import javax.inject.Inject;
@@ -47,6 +48,7 @@ class KeyManagerImpl implements KeyManager, Service, EventListener {
private final Clock clock;
private final Map<ContactId, Boolean> activeContacts;
private final ConcurrentHashMap<TransportId, TransportKeyManager> managers;
private final AtomicBoolean used = new AtomicBoolean(false);
@Inject
KeyManagerImpl(DatabaseComponent db, CryptoComponent crypto,
@@ -66,6 +68,7 @@ class KeyManagerImpl implements KeyManager, Service, EventListener {
@Override
public void startService() throws ServiceException {
if (used.getAndSet(true)) throw new IllegalStateException();
Map<TransportId, Integer> transports =
new HashMap<TransportId, Integer>();
for (SimplexPluginFactory f : pluginConfig.getSimplexFactories())

View File

@@ -30,6 +30,7 @@ import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorCompletionService;
import java.util.concurrent.Future;
import java.util.concurrent.Semaphore;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Logger;
import javax.bluetooth.BluetoothStateException;
@@ -62,6 +63,7 @@ class BluetoothPlugin implements DuplexPlugin {
private final DuplexPluginCallback callback;
private final int maxLatency;
private final Semaphore discoverySemaphore = new Semaphore(1);
private final AtomicBoolean used = new AtomicBoolean(false);
private volatile boolean running = false;
private volatile StreamConnectionNotifier socket = null;
@@ -76,20 +78,25 @@ class BluetoothPlugin implements DuplexPlugin {
this.maxLatency = maxLatency;
}
@Override
public TransportId getId() {
return ID;
}
@Override
public int getMaxLatency() {
return maxLatency;
}
@Override
public int getMaxIdleTime() {
// Bluetooth detects dead connections so we don't need keepalives
return Integer.MAX_VALUE;
}
@Override
public boolean start() throws IOException {
if (used.getAndSet(true)) throw new IllegalStateException();
// Initialise the Bluetooth stack
try {
localDevice = LocalDevice.getLocalDevice();
@@ -108,6 +115,7 @@ class BluetoothPlugin implements DuplexPlugin {
private void bind() {
ioExecutor.execute(new Runnable() {
@Override
public void run() {
if (!running) return;
// Advertise the Bluetooth address to contacts
@@ -183,23 +191,28 @@ class BluetoothPlugin implements DuplexPlugin {
return new BluetoothTransportConnection(this, s);
}
@Override
public void stop() {
running = false;
tryToClose(socket);
}
@Override
public boolean isRunning() {
return running;
}
@Override
public boolean shouldPoll() {
return true;
}
@Override
public int getPollingInterval() {
return backoff.getPollingInterval();
}
@Override
public void poll(final Collection<ContactId> connected) {
if (!running) return;
backoff.increment();
@@ -214,6 +227,7 @@ class BluetoothPlugin implements DuplexPlugin {
final String uuid = e.getValue().get(PROP_UUID);
if (StringUtils.isNullOrEmpty(uuid)) continue;
ioExecutor.execute(new Runnable() {
@Override
public void run() {
if (!running) return;
StreamConnection s = connect(makeUrl(address, uuid));
@@ -238,6 +252,7 @@ class BluetoothPlugin implements DuplexPlugin {
}
}
@Override
public DuplexTransportConnection createConnection(ContactId c) {
if (!running) return null;
TransportProperties p = callback.getRemoteProperties().get(c);
@@ -252,10 +267,12 @@ class BluetoothPlugin implements DuplexPlugin {
return new BluetoothTransportConnection(this, s);
}
@Override
public boolean supportsInvitations() {
return true;
}
@Override
public DuplexTransportConnection createInvitationConnection(PseudoRandom r,
long timeout, boolean alice) {
if (!running) return null;
@@ -279,9 +296,8 @@ class BluetoothPlugin implements DuplexPlugin {
}
// Create the background tasks
CompletionService<StreamConnection> complete =
new ExecutorCompletionService<StreamConnection>(ioExecutor);
List<Future<StreamConnection>> futures =
new ArrayList<Future<StreamConnection>>();
new ExecutorCompletionService<>(ioExecutor);
List<Future<StreamConnection>> futures = new ArrayList<>();
if (alice) {
// Return the first connected socket
futures.add(complete.submit(new ListeningTask(ss)));
@@ -316,6 +332,7 @@ class BluetoothPlugin implements DuplexPlugin {
private void closeSockets(final List<Future<StreamConnection>> futures,
final StreamConnection chosen) {
ioExecutor.execute(new Runnable() {
@Override
public void run() {
for (Future<StreamConnection> f : futures) {
try {
@@ -331,9 +348,7 @@ class BluetoothPlugin implements DuplexPlugin {
} catch (InterruptedException e) {
LOG.info("Interrupted while closing sockets");
return;
} catch (ExecutionException e) {
if (LOG.isLoggable(INFO)) LOG.info(e.toString());
} catch (IOException e) {
} catch (ExecutionException | IOException e) {
if (LOG.isLoggable(INFO)) LOG.info(e.toString());
}
}
@@ -341,14 +356,15 @@ class BluetoothPlugin implements DuplexPlugin {
});
}
@Override
public boolean supportsKeyAgreement() {
return true;
}
public KeyAgreementListener createKeyAgreementListener(
byte[] localCommitment) {
@Override
public KeyAgreementListener createKeyAgreementListener(byte[] commitment) {
// No truncation necessary because COMMIT_LENGTH = 16
String uuid = UUID.nameUUIDFromBytes(localCommitment).toString();
String uuid = UUID.nameUUIDFromBytes(commitment).toString();
if (LOG.isLoggable(INFO)) LOG.info("Key agreement UUID " + uuid);
String url = makeUrl("localhost", uuid);
// Make the device discoverable if possible
@@ -371,8 +387,9 @@ class BluetoothPlugin implements DuplexPlugin {
return new BluetoothKeyAgreementListener(d, ss);
}
@Override
public DuplexTransportConnection createKeyAgreementConnection(
byte[] remoteCommitment, TransportDescriptor d, long timeout) {
byte[] commitment, TransportDescriptor d, long timeout) {
if (!isRunning()) return null;
if (!ID.equals(d.getIdentifier())) return null;
TransportProperties p = d.getProperties();
@@ -380,7 +397,7 @@ class BluetoothPlugin implements DuplexPlugin {
String address = p.get(PROP_ADDRESS);
if (StringUtils.isNullOrEmpty(address)) return null;
// No truncation necessary because COMMIT_LENGTH = 16
String uuid = UUID.nameUUIDFromBytes(remoteCommitment).toString();
String uuid = UUID.nameUUIDFromBytes(commitment).toString();
if (LOG.isLoggable(INFO))
LOG.info("Connecting to key agreement UUID " + uuid);
String url = makeUrl(address, uuid);

View File

@@ -34,29 +34,36 @@ implements RemovableDriveMonitor.Callback {
this.monitor = monitor;
}
@Override
public TransportId getId() {
return ID;
}
@Override
public boolean start() throws IOException {
if (used.getAndSet(true)) throw new IllegalStateException();
running = true;
monitor.start(this);
return true;
}
@Override
public void stop() throws IOException {
running = false;
monitor.stop();
}
@Override
public boolean shouldPoll() {
return false;
}
@Override
public int getPollingInterval() {
throw new UnsupportedOperationException();
}
@Override
public void poll(Collection<ContactId> connected) {
throw new UnsupportedOperationException();
}
@@ -64,8 +71,7 @@ implements RemovableDriveMonitor.Callback {
@Override
protected File chooseOutputDirectory() {
try {
List<File> drives =
new ArrayList<File>(finder.findRemovableDrives());
List<File> drives = new ArrayList<>(finder.findRemovableDrives());
if (drives.isEmpty()) return null;
String[] paths = new String[drives.size()];
for (int i = 0; i < paths.length; i++) {
@@ -92,7 +98,7 @@ implements RemovableDriveMonitor.Callback {
@Override
protected Collection<File> findFilesByName(String filename) {
List<File> matches = new ArrayList<File>();
List<File> matches = new ArrayList<>();
try {
for (File drive : finder.findRemovableDrives()) {
File[] files = drive.listFiles();
@@ -109,6 +115,7 @@ implements RemovableDriveMonitor.Callback {
return Collections.unmodifiableList(matches);
}
@Override
public void driveInserted(File root) {
File[] files = root.listFiles();
if (files != null) {
@@ -116,6 +123,7 @@ implements RemovableDriveMonitor.Callback {
}
}
@Override
public void exceptionThrown(IOException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
}

View File

@@ -16,6 +16,7 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Collection;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Logger;
import static java.util.logging.Level.INFO;
@@ -32,6 +33,7 @@ class ModemPlugin implements DuplexPlugin, Modem.Callback {
private final SerialPortList serialPortList;
private final DuplexPluginCallback callback;
private final int maxLatency;
private final AtomicBoolean used = new AtomicBoolean(false);
private volatile boolean running = false;
private volatile Modem modem = null;
@@ -44,20 +46,25 @@ class ModemPlugin implements DuplexPlugin, Modem.Callback {
this.maxLatency = maxLatency;
}
@Override
public TransportId getId() {
return ID;
}
@Override
public int getMaxLatency() {
return maxLatency;
}
@Override
public int getMaxIdleTime() {
// FIXME: Do we need keepalives for this transport?
return Integer.MAX_VALUE;
}
@Override
public boolean start() {
if (used.getAndSet(true)) throw new IllegalStateException();
for (String portName : serialPortList.getPortNames()) {
if (LOG.isLoggable(INFO))
LOG.info("Trying to initialise modem on " + portName);
@@ -75,6 +82,7 @@ class ModemPlugin implements DuplexPlugin, Modem.Callback {
return false;
}
@Override
public void stop() {
running = false;
if (modem != null) {
@@ -86,18 +94,22 @@ class ModemPlugin implements DuplexPlugin, Modem.Callback {
}
}
@Override
public boolean isRunning() {
return running;
}
@Override
public boolean shouldPoll() {
return false;
}
@Override
public int getPollingInterval() {
throw new UnsupportedOperationException();
}
@Override
public void poll(Collection<ContactId> connected) {
throw new UnsupportedOperationException();
}
@@ -121,6 +133,7 @@ class ModemPlugin implements DuplexPlugin, Modem.Callback {
return false;
}
@Override
public DuplexTransportConnection createConnection(ContactId c) {
if (!running) return null;
// Get the ISO 3166 code for the caller's country
@@ -148,29 +161,34 @@ class ModemPlugin implements DuplexPlugin, Modem.Callback {
return new ModemTransportConnection();
}
@Override
public boolean supportsInvitations() {
return false;
}
@Override
public DuplexTransportConnection createInvitationConnection(PseudoRandom r,
long timeout, boolean alice) {
throw new UnsupportedOperationException();
}
@Override
public boolean supportsKeyAgreement() {
return false;
}
public KeyAgreementListener createKeyAgreementListener(
byte[] commitment) {
@Override
public KeyAgreementListener createKeyAgreementListener(byte[] commitment) {
throw new UnsupportedOperationException();
}
@Override
public DuplexTransportConnection createKeyAgreementConnection(
byte[] commitment, TransportDescriptor d, long timeout) {
throw new UnsupportedOperationException();
}
@Override
public void incomingCallConnected() {
LOG.info("Incoming call connected");
callback.incomingConnectionCreated(new ModemTransportConnection());