diff --git a/briar-android/res/values/roboguice.xml b/briar-android/res/values/roboguice.xml
index 0f6ffcd915b60127967ef02d69fd86bc8c3110db..32110839f6c3ba3da878e67e248b2199e2918b55 100644
--- a/briar-android/res/values/roboguice.xml
+++ b/briar-android/res/values/roboguice.xml
@@ -10,7 +10,6 @@
 		<item>org.briarproject.messaging.duplex.DuplexMessagingModule</item>
 		<item>org.briarproject.messaging.simplex.SimplexMessagingModule</item>
 		<item>org.briarproject.plugins.AndroidPluginsModule</item>
-		<item>org.briarproject.plugins.PluginsModule</item>
 		<item>org.briarproject.serial.SerialModule</item>
 		<item>org.briarproject.system.AndroidSystemModule</item>
 		<item>org.briarproject.transport.TransportModule</item>
diff --git a/briar-android/src/org/briarproject/android/AndroidModule.java b/briar-android/src/org/briarproject/android/AndroidModule.java
index 4192956bf456adeccd17c40690d53b5df9baeccf..d3b2029e7039701823116869e65f994464520a69 100644
--- a/briar-android/src/org/briarproject/android/AndroidModule.java
+++ b/briar-android/src/org/briarproject/android/AndroidModule.java
@@ -1,21 +1,13 @@
 package org.briarproject.android;
 
 import static android.content.Context.MODE_PRIVATE;
-import static java.util.concurrent.TimeUnit.SECONDS;
 
 import java.io.File;
-import java.util.concurrent.BlockingQueue;
-import java.util.concurrent.Executor;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.LinkedBlockingQueue;
-import java.util.concurrent.RejectedExecutionHandler;
-import java.util.concurrent.ThreadPoolExecutor;
 
 import javax.inject.Singleton;
 
 import org.briarproject.api.android.AndroidExecutor;
 import org.briarproject.api.android.AndroidNotificationManager;
-import org.briarproject.api.android.DatabaseUiExecutor;
 import org.briarproject.api.android.ReferenceManager;
 import org.briarproject.api.db.DatabaseConfig;
 import org.briarproject.api.lifecycle.LifecycleManager;
@@ -28,18 +20,9 @@ import com.google.inject.Provides;
 
 public class AndroidModule extends AbstractModule {
 
-	private final ExecutorService databaseUiExecutor;
 	private final UiCallback uiCallback;
 
 	public AndroidModule() {
-		// The queue is unbounded, so tasks can be dependent
-		BlockingQueue<Runnable> queue = new LinkedBlockingQueue<Runnable>();
-		// Discard tasks that are submitted during shutdown
-		RejectedExecutionHandler policy =
-				new ThreadPoolExecutor.DiscardPolicy();
-		// Use a single thread so DB accesses from the UI don't overlap
-		databaseUiExecutor = new ThreadPoolExecutor(1, 1, 60, SECONDS, queue,
-				policy);
 		// Use a dummy UI callback
 		uiCallback = new UiCallback() {
 
@@ -53,10 +36,11 @@ public class AndroidModule extends AbstractModule {
 
 			public void showMessage(String... message) {
 				throw new UnsupportedOperationException();
-			}			
+			}
 		};
 	}
 
+	@Override
 	protected void configure() {
 		bind(AndroidExecutor.class).to(AndroidExecutorImpl.class).in(
 				Singleton.class);
@@ -65,12 +49,6 @@ public class AndroidModule extends AbstractModule {
 		bind(UiCallback.class).toInstance(uiCallback);
 	}
 
-	@Provides @Singleton @DatabaseUiExecutor
-	Executor getDatabaseUiExecutor(LifecycleManager lifecycleManager) {
-		lifecycleManager.registerForShutdown(databaseUiExecutor);
-		return databaseUiExecutor;
-	}
-
 	@Provides @Singleton
 	DatabaseConfig getDatabaseConfig(final Application app) {
 		final File dir = app.getApplicationContext().getDir("db", MODE_PRIVATE);
diff --git a/briar-android/src/org/briarproject/android/AndroidNotificationManagerImpl.java b/briar-android/src/org/briarproject/android/AndroidNotificationManagerImpl.java
index 708c74b58cc2168044946e046f5a16b06c106179..f65d71f704fd88514ca4d983bcac98663dd7981a 100644
--- a/briar-android/src/org/briarproject/android/AndroidNotificationManagerImpl.java
+++ b/briar-android/src/org/briarproject/android/AndroidNotificationManagerImpl.java
@@ -23,8 +23,8 @@ import org.briarproject.android.groups.GroupListActivity;
 import org.briarproject.api.ContactId;
 import org.briarproject.api.Settings;
 import org.briarproject.api.android.AndroidNotificationManager;
-import org.briarproject.api.android.DatabaseUiExecutor;
 import org.briarproject.api.db.DatabaseComponent;
+import org.briarproject.api.db.DatabaseExecutor;
 import org.briarproject.api.db.DbException;
 import org.briarproject.api.event.Event;
 import org.briarproject.api.event.EventListener;
@@ -51,7 +51,7 @@ Service, EventListener {
 			Logger.getLogger(AndroidNotificationManagerImpl.class.getName());
 
 	private final DatabaseComponent db;
-	private final Executor dbUiExecutor;
+	private final Executor dbExecutor;
 	private final Context appContext;
 	private final Map<ContactId, Integer> contactCounts =
 			new HashMap<ContactId, Integer>(); // Locking: this
@@ -65,9 +65,9 @@ Service, EventListener {
 
 	@Inject
 	public AndroidNotificationManagerImpl(DatabaseComponent db,
-			@DatabaseUiExecutor Executor dbExecutor, Application app) {
+			@DatabaseExecutor Executor dbExecutor, Application app) {
 		this.db = db;
-		this.dbUiExecutor = dbExecutor;
+		this.dbExecutor = dbExecutor;
 		appContext = app.getApplicationContext();
 	}
 
@@ -78,7 +78,7 @@ Service, EventListener {
 	}
 
 	private void loadSettings() {
-		dbUiExecutor.execute(new Runnable() {
+		dbExecutor.execute(new Runnable() {
 			public void run() {
 				try {
 					settings = db.getSettings();
diff --git a/briar-android/src/org/briarproject/android/BriarActivity.java b/briar-android/src/org/briarproject/android/BriarActivity.java
index c753015fa74ce6133a2974c925bf537b0f2ae43d..eae384e53dc508ab0cc9f413647442518f80b339 100644
--- a/briar-android/src/org/briarproject/android/BriarActivity.java
+++ b/briar-android/src/org/briarproject/android/BriarActivity.java
@@ -5,14 +5,18 @@ import static android.content.Intent.FLAG_ACTIVITY_SINGLE_TOP;
 import static android.view.WindowManager.LayoutParams.FLAG_SECURE;
 import static android.view.inputmethod.InputMethodManager.HIDE_IMPLICIT_ONLY;
 import static org.briarproject.android.TestingConstants.PREVENT_SCREENSHOTS;
+
 import java.util.concurrent.Executor;
 import java.util.logging.Logger;
+
 import javax.inject.Inject;
+
 import org.briarproject.android.BriarService.BriarBinder;
 import org.briarproject.android.BriarService.BriarServiceConnection;
-import org.briarproject.api.android.DatabaseUiExecutor;
 import org.briarproject.api.db.DatabaseConfig;
+import org.briarproject.api.db.DatabaseExecutor;
 import org.briarproject.api.lifecycle.LifecycleManager;
+
 import roboguice.activity.RoboActivity;
 import android.annotation.SuppressLint;
 import android.content.Intent;
@@ -35,7 +39,7 @@ public class BriarActivity extends RoboActivity {
 	private boolean bound = false;
 
 	// Fields that are accessed from background threads must be volatile
-	@Inject @DatabaseUiExecutor private volatile Executor dbUiExecutor;
+	@Inject @DatabaseExecutor private volatile Executor dbExecutor;
 	@Inject private volatile LifecycleManager lifecycleManager;
 
 	@Override
@@ -113,7 +117,7 @@ public class BriarActivity extends RoboActivity {
 	}
 
 	protected void runOnDbThread(final Runnable task) {
-		dbUiExecutor.execute(new Runnable() {
+		dbExecutor.execute(new Runnable() {
 			public void run() {
 				try {
 					lifecycleManager.waitForDatabase();
diff --git a/briar-android/src/org/briarproject/android/BriarService.java b/briar-android/src/org/briarproject/android/BriarService.java
index 2e606f42eb79e6fd1b3ff21668120f7a8143c1cb..36493ee44d967309634fff8e84e140cd2b2e0d81 100644
--- a/briar-android/src/org/briarproject/android/BriarService.java
+++ b/briar-android/src/org/briarproject/android/BriarService.java
@@ -18,9 +18,9 @@ import org.briarproject.R;
 import org.briarproject.api.ContactId;
 import org.briarproject.api.android.AndroidExecutor;
 import org.briarproject.api.android.AndroidNotificationManager;
-import org.briarproject.api.android.DatabaseUiExecutor;
 import org.briarproject.api.db.DatabaseComponent;
 import org.briarproject.api.db.DatabaseConfig;
+import org.briarproject.api.db.DatabaseExecutor;
 import org.briarproject.api.db.DbException;
 import org.briarproject.api.event.Event;
 import org.briarproject.api.event.EventListener;
@@ -56,7 +56,7 @@ public class BriarService extends RoboService implements EventListener {
 	// Fields that are accessed from background threads must be volatile
 	@Inject private volatile LifecycleManager lifecycleManager;
 	@Inject private volatile AndroidExecutor androidExecutor;
-	@Inject @DatabaseUiExecutor private volatile Executor dbUiExecutor;
+	@Inject @DatabaseExecutor private volatile Executor dbExecutor;
 	@Inject private volatile DatabaseComponent db;
 	private volatile boolean started = false;
 
@@ -130,6 +130,7 @@ public class BriarService extends RoboService implements EventListener {
 		return START_NOT_STICKY; // Don't restart automatically if killed
 	}
 
+	@Override
 	public IBinder onBind(Intent intent) {
 		return binder;
 	}
@@ -170,7 +171,7 @@ public class BriarService extends RoboService implements EventListener {
 	}
 
 	private void showMessageNotification(final GroupId g, final ContactId c) {
-		dbUiExecutor.execute(new Runnable() {
+		dbExecutor.execute(new Runnable() {
 			public void run() {
 				try {
 					lifecycleManager.waitForDatabase();
diff --git a/briar-android/src/org/briarproject/android/SplashScreenActivity.java b/briar-android/src/org/briarproject/android/SplashScreenActivity.java
index ff48381cd3161747087e41f9eedacce3069f997a..c0b7a15146caa7a7fab23caabfd13298378f69f1 100644
--- a/briar-android/src/org/briarproject/android/SplashScreenActivity.java
+++ b/briar-android/src/org/briarproject/android/SplashScreenActivity.java
@@ -30,8 +30,8 @@ public class SplashScreenActivity extends RoboSplashActivity {
 	private static final Logger LOG =
 			Logger.getLogger(SplashScreenActivity.class.getName());
 
-	// This build expires on 12 July 2014
-	private static final long EXPIRY_DATE = 1405123200 * 1000L;
+	// This build expires on 8 October 2014
+	private static final long EXPIRY_DATE = 1412726400 * 1000L;
 
 	private long now = System.currentTimeMillis();
 
diff --git a/briar-android/src/org/briarproject/plugins/AndroidPluginsModule.java b/briar-android/src/org/briarproject/plugins/AndroidPluginsModule.java
index 25f69512829e69f718f54cad4ad2fd57acbd2217..12624c0dd397a0e0fa6e9b66c7f3ac2bab6b05d7 100644
--- a/briar-android/src/org/briarproject/plugins/AndroidPluginsModule.java
+++ b/briar-android/src/org/briarproject/plugins/AndroidPluginsModule.java
@@ -7,7 +7,7 @@ import java.util.concurrent.Executor;
 
 import org.briarproject.api.android.AndroidExecutor;
 import org.briarproject.api.crypto.CryptoComponent;
-import org.briarproject.api.plugins.PluginExecutor;
+import org.briarproject.api.lifecycle.IoExecutor;
 import org.briarproject.api.plugins.duplex.DuplexPluginConfig;
 import org.briarproject.api.plugins.duplex.DuplexPluginFactory;
 import org.briarproject.api.plugins.simplex.SimplexPluginConfig;
@@ -20,13 +20,9 @@ import org.briarproject.plugins.tor.TorPluginFactory;
 import android.app.Application;
 import android.content.Context;
 
-import com.google.inject.AbstractModule;
 import com.google.inject.Provides;
 
-public class AndroidPluginsModule extends AbstractModule {
-
-	@Override
-	protected void configure() {}
+public class AndroidPluginsModule extends PluginsModule {
 
 	@Provides
 	SimplexPluginConfig getSimplexPluginConfig() {
@@ -38,18 +34,16 @@ public class AndroidPluginsModule extends AbstractModule {
 	}
 
 	@Provides
-	DuplexPluginConfig getDuplexPluginConfig(
-			@PluginExecutor Executor pluginExecutor,
+	DuplexPluginConfig getDuplexPluginConfig(@IoExecutor Executor ioExecutor,
 			AndroidExecutor androidExecutor, Application app,
 			CryptoComponent crypto, LocationUtils locationUtils) {
 		Context appContext = app.getApplicationContext();
-		DuplexPluginFactory bluetooth = new DroidtoothPluginFactory(
-				pluginExecutor, androidExecutor, appContext,
-				crypto.getSecureRandom());
-		DuplexPluginFactory tor = new TorPluginFactory(pluginExecutor,
-				appContext, locationUtils);
-		DuplexPluginFactory lan = new AndroidLanTcpPluginFactory(
-				pluginExecutor, appContext);
+		DuplexPluginFactory bluetooth = new DroidtoothPluginFactory(ioExecutor,
+				androidExecutor, appContext, crypto.getSecureRandom());
+		DuplexPluginFactory tor = new TorPluginFactory(ioExecutor, appContext,
+				locationUtils);
+		DuplexPluginFactory lan = new AndroidLanTcpPluginFactory(ioExecutor,
+				appContext);
 		final Collection<DuplexPluginFactory> factories =
 				Arrays.asList(bluetooth, tor, lan);
 		return new DuplexPluginConfig() {
diff --git a/briar-android/src/org/briarproject/plugins/droidtooth/DroidtoothPlugin.java b/briar-android/src/org/briarproject/plugins/droidtooth/DroidtoothPlugin.java
index aaa38352b64f9c5fab29837543d98468ade58897..1b65e45c8717b24e39f9f41baffe93a2614cd6d0 100644
--- a/briar-android/src/org/briarproject/plugins/droidtooth/DroidtoothPlugin.java
+++ b/briar-android/src/org/briarproject/plugins/droidtooth/DroidtoothPlugin.java
@@ -63,7 +63,7 @@ class DroidtoothPlugin implements DuplexPlugin {
 	private static final String DISCOVERY_FINISHED =
 			"android.bluetooth.adapter.action.DISCOVERY_FINISHED";
 
-	private final Executor pluginExecutor;
+	private final Executor ioExecutor;
 	private final AndroidExecutor androidExecutor;
 	private final Context appContext;
 	private final SecureRandom secureRandom;
@@ -80,11 +80,11 @@ class DroidtoothPlugin implements DuplexPlugin {
 	// Non-null if the plugin started successfully
 	private volatile BluetoothAdapter adapter = null;
 
-	DroidtoothPlugin(Executor pluginExecutor, AndroidExecutor androidExecutor,
+	DroidtoothPlugin(Executor ioExecutor, AndroidExecutor androidExecutor,
 			Context appContext, SecureRandom secureRandom, Clock clock,
 			DuplexPluginCallback callback, int maxFrameLength, long maxLatency,
 			long pollingInterval) {
-		this.pluginExecutor = pluginExecutor;
+		this.ioExecutor = ioExecutor;
 		this.androidExecutor = androidExecutor;
 		this.appContext = appContext;
 		this.secureRandom = secureRandom;
@@ -147,7 +147,7 @@ class DroidtoothPlugin implements DuplexPlugin {
 	}
 
 	private void bind() {
-		pluginExecutor.execute(new Runnable() {
+		ioExecutor.execute(new Runnable() {
 			public void run() {
 				if(!isRunning()) return;
 				if(LOG.isLoggable(INFO))
@@ -256,7 +256,7 @@ class DroidtoothPlugin implements DuplexPlugin {
 			if(StringUtils.isNullOrEmpty(address)) continue;
 			final String uuid = e.getValue().get("uuid");
 			if(StringUtils.isNullOrEmpty(uuid)) continue;
-			pluginExecutor.execute(new Runnable() {
+			ioExecutor.execute(new Runnable() {
 				public void run() {
 					if(!running) return;
 					BluetoothSocket s = connect(address, uuid);
diff --git a/briar-android/src/org/briarproject/plugins/droidtooth/DroidtoothPluginFactory.java b/briar-android/src/org/briarproject/plugins/droidtooth/DroidtoothPluginFactory.java
index 4015185fa300dd6056bde1bf91f1dcd32cbd580b..b7bb2028546aaa3dc508cffdbde4da0ab9bb3835 100644
--- a/briar-android/src/org/briarproject/plugins/droidtooth/DroidtoothPluginFactory.java
+++ b/briar-android/src/org/briarproject/plugins/droidtooth/DroidtoothPluginFactory.java
@@ -19,16 +19,16 @@ public class DroidtoothPluginFactory implements DuplexPluginFactory {
 	private static final long MAX_LATENCY = 60 * 1000; // 1 minute
 	private static final long POLLING_INTERVAL = 3 * 60 * 1000; // 3 minutes
 
-	private final Executor pluginExecutor;
+	private final Executor ioExecutor;
 	private final AndroidExecutor androidExecutor;
 	private final Context appContext;
 	private final SecureRandom secureRandom;
 	private final Clock clock;
 
-	public DroidtoothPluginFactory(Executor pluginExecutor,
+	public DroidtoothPluginFactory(Executor ioExecutor,
 			AndroidExecutor androidExecutor, Context appContext,
 			SecureRandom secureRandom) {
-		this.pluginExecutor = pluginExecutor;
+		this.ioExecutor = ioExecutor;
 		this.androidExecutor = androidExecutor;
 		this.appContext = appContext;
 		this.secureRandom = secureRandom;
@@ -40,7 +40,7 @@ public class DroidtoothPluginFactory implements DuplexPluginFactory {
 	}
 
 	public DuplexPlugin createPlugin(DuplexPluginCallback callback) {
-		return new DroidtoothPlugin(pluginExecutor, androidExecutor, appContext,
+		return new DroidtoothPlugin(ioExecutor, androidExecutor, appContext,
 				secureRandom, clock, callback, MAX_FRAME_LENGTH, MAX_LATENCY,
 				POLLING_INTERVAL);
 	}
diff --git a/briar-android/src/org/briarproject/plugins/tcp/AndroidLanTcpPlugin.java b/briar-android/src/org/briarproject/plugins/tcp/AndroidLanTcpPlugin.java
index 5092d4537744f0157d2709349ea881eca553ac9b..eaec50c41c0873622e5ae4da61fac973c90132e3 100644
--- a/briar-android/src/org/briarproject/plugins/tcp/AndroidLanTcpPlugin.java
+++ b/briar-android/src/org/briarproject/plugins/tcp/AndroidLanTcpPlugin.java
@@ -25,10 +25,10 @@ class AndroidLanTcpPlugin extends LanTcpPlugin {
 
 	private volatile BroadcastReceiver networkStateReceiver = null;
 
-	AndroidLanTcpPlugin(Executor pluginExecutor, Context appContext,
+	AndroidLanTcpPlugin(Executor ioExecutor, Context appContext,
 			DuplexPluginCallback callback, int maxFrameLength, long maxLatency,
 			long pollingInterval) {
-		super(pluginExecutor, callback, maxFrameLength, maxLatency,
+		super(ioExecutor, callback, maxFrameLength, maxLatency,
 				pollingInterval);
 		this.appContext = appContext;
 	}
diff --git a/briar-android/src/org/briarproject/plugins/tcp/AndroidLanTcpPluginFactory.java b/briar-android/src/org/briarproject/plugins/tcp/AndroidLanTcpPluginFactory.java
index dec3bf711e0f03b0773d45c15fcc6a0f5676d73b..a51326e55bb441263c69f2191935408d5b1b621f 100644
--- a/briar-android/src/org/briarproject/plugins/tcp/AndroidLanTcpPluginFactory.java
+++ b/briar-android/src/org/briarproject/plugins/tcp/AndroidLanTcpPluginFactory.java
@@ -15,12 +15,11 @@ public class AndroidLanTcpPluginFactory implements DuplexPluginFactory {
 	private static final long MAX_LATENCY = 60 * 1000; // 1 minute
 	private static final long POLLING_INTERVAL = 60 * 1000; // 1 minute
 
-	private final Executor pluginExecutor;
+	private final Executor ioExecutor;
 	private final Context appContext;
 
-	public AndroidLanTcpPluginFactory(Executor pluginExecutor,
-			Context appContext) {
-		this.pluginExecutor = pluginExecutor;
+	public AndroidLanTcpPluginFactory(Executor ioExecutor, Context appContext) {
+		this.ioExecutor = ioExecutor;
 		this.appContext = appContext;
 	}
 
@@ -29,7 +28,7 @@ public class AndroidLanTcpPluginFactory implements DuplexPluginFactory {
 	}
 
 	public DuplexPlugin createPlugin(DuplexPluginCallback callback) {
-		return new AndroidLanTcpPlugin(pluginExecutor, appContext, callback,
+		return new AndroidLanTcpPlugin(ioExecutor, appContext, callback,
 				MAX_FRAME_LENGTH, MAX_LATENCY, POLLING_INTERVAL);
 	}
 }
diff --git a/briar-android/src/org/briarproject/plugins/tor/TorPlugin.java b/briar-android/src/org/briarproject/plugins/tor/TorPlugin.java
index c8b500975ed394594ea38e4a9c4f1994536da0db..f8c225e9eed02f03f2eb02af0cb96376a2c97e05 100644
--- a/briar-android/src/org/briarproject/plugins/tor/TorPlugin.java
+++ b/briar-android/src/org/briarproject/plugins/tor/TorPlugin.java
@@ -72,7 +72,7 @@ class TorPlugin implements DuplexPlugin, EventHandler {
 	private static final Logger LOG =
 			Logger.getLogger(TorPlugin.class.getName());
 
-	private final Executor pluginExecutor;
+	private final Executor ioExecutor;
 	private final Context appContext;
 	private final LocationUtils locationUtils;
 	private final DuplexPluginCallback callback;
@@ -89,10 +89,10 @@ class TorPlugin implements DuplexPlugin, EventHandler {
 	private volatile TorControlConnection controlConnection = null;
 	private volatile BroadcastReceiver networkStateReceiver = null;
 
-	TorPlugin(Executor pluginExecutor, Context appContext,
+	TorPlugin(Executor ioExecutor, Context appContext,
 			LocationUtils locationUtils, DuplexPluginCallback callback,
 			int maxFrameLength, long maxLatency, long pollingInterval) {
-		this.pluginExecutor = pluginExecutor;
+		this.ioExecutor = ioExecutor;
 		this.appContext = appContext;
 		this.locationUtils = locationUtils;
 		this.callback = callback;
@@ -351,7 +351,7 @@ class TorPlugin implements DuplexPlugin, EventHandler {
 	}
 
 	private void bind() {
-		pluginExecutor.execute(new Runnable() {
+		ioExecutor.execute(new Runnable() {
 			public void run() {
 				// If there's already a port number stored in config, reuse it
 				String portString = callback.getConfig().get("port");
@@ -379,7 +379,7 @@ class TorPlugin implements DuplexPlugin, EventHandler {
 				c.put("port", localPort);
 				callback.mergeConfig(c);
 				// Create a hidden service if necessary
-				pluginExecutor.execute(new Runnable() {
+				ioExecutor.execute(new Runnable() {
 					public void run() {
 						publishHiddenService(localPort);
 					}
@@ -506,7 +506,7 @@ class TorPlugin implements DuplexPlugin, EventHandler {
 	}
 
 	private void connectAndCallBack(final ContactId c) {
-		pluginExecutor.execute(new Runnable() {
+		ioExecutor.execute(new Runnable() {
 			public void run() {
 				DuplexTransportConnection d = createConnection(c);
 				if(d != null) callback.outgoingConnectionCreated(c, d);
diff --git a/briar-android/src/org/briarproject/plugins/tor/TorPluginFactory.java b/briar-android/src/org/briarproject/plugins/tor/TorPluginFactory.java
index e520e25673dc2a83a67ceb61aacb9204ce3bd5a7..b7c08b225a72c1d0dea4c73a09e9f9118216a68b 100644
--- a/briar-android/src/org/briarproject/plugins/tor/TorPluginFactory.java
+++ b/briar-android/src/org/briarproject/plugins/tor/TorPluginFactory.java
@@ -21,13 +21,13 @@ public class TorPluginFactory implements DuplexPluginFactory {
 	private static final long MAX_LATENCY = 60 * 1000; // 1 minute
 	private static final long POLLING_INTERVAL = 3 * 60 * 1000; // 3 minutes
 
-	private final Executor pluginExecutor;
+	private final Executor ioExecutor;
 	private final Context appContext;
 	private final LocationUtils locationUtils;
 
-	public TorPluginFactory(Executor pluginExecutor, Context appContext,
+	public TorPluginFactory(Executor ioExecutor, Context appContext,
 			LocationUtils locationUtils) {
-		this.pluginExecutor = pluginExecutor;
+		this.ioExecutor = ioExecutor;
 		this.appContext = appContext;
 		this.locationUtils = locationUtils;
 	}
@@ -42,7 +42,7 @@ public class TorPluginFactory implements DuplexPluginFactory {
 			LOG.info("Tor is not supported on this architecture");
 			return null;
 		}
-		return new TorPlugin(pluginExecutor,appContext, locationUtils,
-				callback, MAX_FRAME_LENGTH, MAX_LATENCY, POLLING_INTERVAL);
+		return new TorPlugin(ioExecutor,appContext, locationUtils, callback,
+				MAX_FRAME_LENGTH, MAX_LATENCY, POLLING_INTERVAL);
 	}
 }
diff --git a/briar-api/src/org/briarproject/api/android/DatabaseUiExecutor.java b/briar-api/src/org/briarproject/api/android/DatabaseUiExecutor.java
deleted file mode 100644
index 4a9ceb4eb27e69e9d6faf99105f482a25f203796..0000000000000000000000000000000000000000
--- a/briar-api/src/org/briarproject/api/android/DatabaseUiExecutor.java
+++ /dev/null
@@ -1,19 +0,0 @@
-package org.briarproject.api.android;
-
-import static java.lang.annotation.ElementType.FIELD;
-import static java.lang.annotation.ElementType.METHOD;
-import static java.lang.annotation.ElementType.PARAMETER;
-import static java.lang.annotation.RetentionPolicy.RUNTIME;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.Target;
-
-import com.google.inject.BindingAnnotation;
-
-/**
- * Annotation for injecting the executor for accessing the database from the UI.
- */
-@BindingAnnotation
-@Target({ FIELD, METHOD, PARAMETER })
-@Retention(RUNTIME)
-public @interface DatabaseUiExecutor {}
diff --git a/briar-api/src/org/briarproject/api/plugins/PluginExecutor.java b/briar-api/src/org/briarproject/api/lifecycle/IoExecutor.java
similarity index 74%
rename from briar-api/src/org/briarproject/api/plugins/PluginExecutor.java
rename to briar-api/src/org/briarproject/api/lifecycle/IoExecutor.java
index fb0892ebf39b65ff8bc33de90a45c6047672f3ba..01e0ea49959963d8a85b0f07a0301572faa88b96 100644
--- a/briar-api/src/org/briarproject/api/plugins/PluginExecutor.java
+++ b/briar-api/src/org/briarproject/api/lifecycle/IoExecutor.java
@@ -1,4 +1,4 @@
-package org.briarproject.api.plugins;
+package org.briarproject.api.lifecycle;
 
 import static java.lang.annotation.ElementType.FIELD;
 import static java.lang.annotation.ElementType.METHOD;
@@ -10,8 +10,8 @@ import java.lang.annotation.Target;
 
 import com.google.inject.BindingAnnotation;
 
-/** Annotation for injecting the executor used by transport plugins. */
+/** Annotation for injecting the executor used by long-lived IO tasks. */
 @BindingAnnotation
 @Target({ FIELD, METHOD, PARAMETER })
 @Retention(RUNTIME)
-public @interface PluginExecutor {}
\ No newline at end of file
+public @interface IoExecutor {}
\ No newline at end of file
diff --git a/briar-api/src/org/briarproject/api/reliability/ReliabilityExecutor.java b/briar-api/src/org/briarproject/api/reliability/ReliabilityExecutor.java
deleted file mode 100644
index dbb2ddcd56996853c68c1a3d453c4b8324ba38f3..0000000000000000000000000000000000000000
--- a/briar-api/src/org/briarproject/api/reliability/ReliabilityExecutor.java
+++ /dev/null
@@ -1,17 +0,0 @@
-package org.briarproject.api.reliability;
-
-import static java.lang.annotation.ElementType.FIELD;
-import static java.lang.annotation.ElementType.METHOD;
-import static java.lang.annotation.ElementType.PARAMETER;
-import static java.lang.annotation.RetentionPolicy.RUNTIME;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.Target;
-
-import com.google.inject.BindingAnnotation;
-
-/** Annotation for injecting the executor used by reliability layers. */
-@BindingAnnotation
-@Target({ FIELD, METHOD, PARAMETER })
-@Retention(RUNTIME)
-public @interface ReliabilityExecutor {}
\ No newline at end of file
diff --git a/briar-api/src/org/briarproject/api/transport/IncomingConnectionExecutor.java b/briar-api/src/org/briarproject/api/transport/IncomingConnectionExecutor.java
deleted file mode 100644
index d70554de648e4c276f4464ee9d88da8c8dad2e81..0000000000000000000000000000000000000000
--- a/briar-api/src/org/briarproject/api/transport/IncomingConnectionExecutor.java
+++ /dev/null
@@ -1,19 +0,0 @@
-package org.briarproject.api.transport;
-
-import static java.lang.annotation.ElementType.FIELD;
-import static java.lang.annotation.ElementType.METHOD;
-import static java.lang.annotation.ElementType.PARAMETER;
-import static java.lang.annotation.RetentionPolicy.RUNTIME;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.Target;
-
-import com.google.inject.BindingAnnotation;
-
-/**
- * Annotation for injecting the executor for recognising incoming connections.
- */
-@BindingAnnotation
-@Target({ FIELD, METHOD, PARAMETER })
-@Retention(RUNTIME)
-public @interface IncomingConnectionExecutor {}
\ No newline at end of file
diff --git a/briar-core/src/org/briarproject/db/Database.java b/briar-core/src/org/briarproject/db/Database.java
index ad645ddfb10531f4d2c627ae825239b9f7a86a2f..ad0fb223e0fe77e67e9db09977a4e0747bc73e0c 100644
--- a/briar-core/src/org/briarproject/db/Database.java
+++ b/briar-core/src/org/briarproject/db/Database.java
@@ -38,29 +38,22 @@ import org.briarproject.api.transport.TemporarySecret;
  * terminated by calling either {@link #abortTransaction(T)} or
  * {@link #commitTransaction(T)}, even if an exception is thrown.
  * <p>
- * Locking is provided by the DatabaseComponent implementation. To prevent
- * deadlock, locks must be acquired in the following (alphabetical) order:
- * <ul>
- * <li> contact
- * <li> identity
- * <li> message
- * <li> retention
- * <li> setting
- * <li> subscription
- * <li> transport
- * <li> window
- * </ul>
- * If table A has a foreign key pointing to table B, we get a read lock on A to
- * read A, a write lock on A to write A, and write locks on A and B to write B.
+ * Read-write locking is provided by the DatabaseComponent implementation.
  */
 interface Database<T> {
 
-	/** Opens the database and returns true if the database already existed. */
+	/**
+	 * Opens the database and returns true if the database already existed.
+	 * <p>
+	 * Locking: write.
+	 */
 	boolean open() throws DbException, IOException;
 
 	/**
 	 * Prevents new transactions from starting, waits for all current
 	 * transactions to finish, and closes the database.
+	 * <p>
+	 * Locking: write.
 	 */
 	void close() throws DbException, IOException;
 
@@ -92,8 +85,7 @@ interface Database<T> {
 	 * Stores a contact associated with the given local and remote pseudonyms,
 	 * and returns an ID for the contact.
 	 * <p>
-	 * Locking: contact write, message write, retention write,
-	 * subscription write, transport write, window write.
+	 * Locking: write.
 	 */
 	ContactId addContact(T txn, Author remote, AuthorId local)
 			throws DbException;
@@ -101,7 +93,7 @@ interface Database<T> {
 	/**
 	 * Stores an endpoint.
 	 * <p>
-	 * Locking: window write.
+	 * Locking: write.
 	 */
 	void addEndpoint(T txn, Endpoint ep) throws DbException;
 
@@ -109,29 +101,28 @@ interface Database<T> {
 	 * Subscribes to a group, or returns false if the user already has the
 	 * maximum number of subscriptions.
 	 * <p>
-	 * Locking: message write, subscription write.
+	 * Locking: write.
 	 */
 	boolean addGroup(T txn, Group g) throws DbException;
 
 	/**
 	 * Stores a local pseudonym.
 	 * <p>
-	 * Locking: contact write, identity write, message write, retention write,
-	 * subscription write, transport write, window write.
+	 * Locking: write.
 	 */
 	void addLocalAuthor(T txn, LocalAuthor a) throws DbException;
 
 	/**
 	 * Stores a message.
 	 * <p>
-	 * Locking: message write.
+	 * Locking: write.
 	 */
 	void addMessage(T txn, Message m, boolean local) throws DbException;
 
 	/**
 	 * Records that a message has been offered by the given contact.
 	 * <p>
-	 * Locking: message write.
+	 * Locking: write.
 	 */
 	void addOfferedMessage(T txn, ContactId c, MessageId m) throws DbException;
 
@@ -139,7 +130,7 @@ interface Database<T> {
 	 * Stores the given temporary secrets and deletes any secrets that have
 	 * been made obsolete.
 	 * <p>
-	 * Locking: window write.
+	 * Locking: write.
 	 */
 	void addSecrets(T txn, Collection<TemporarySecret> secrets)
 			throws DbException;
@@ -147,10 +138,10 @@ interface Database<T> {
 	/**
 	 * Initialises the status of the given message with respect to the given
 	 * contact.
+	 * <p>
+	 * Locking: write.
 	 * @param ack whether the message needs to be acknowledged.
 	 * @param seen whether the contact has seen the message.
-	 * <p>
-	 * Locking: message write.
 	 */
 	void addStatus(T txn, ContactId c, MessageId m, boolean ack, boolean seen)
 			throws DbException;
@@ -159,7 +150,7 @@ interface Database<T> {
 	 * Stores a transport and returns true if the transport was not previously
 	 * in the database.
 	 * <p>
-	 * Locking: transport write, window write.
+	 * Locking: write.
 	 */
 	boolean addTransport(T txn, TransportId t, long maxLatency)
 			throws DbException;
@@ -167,49 +158,49 @@ interface Database<T> {
 	/**
 	 * Makes a group visible to the given contact.
 	 * <p>
-	 * Locking: subscription write.
+	 * Locking: write.
 	 */
 	void addVisibility(T txn, ContactId c, GroupId g) throws DbException;
 
 	/**
 	 * Returns true if the database contains the given contact.
 	 * <p>
-	 * Locking: contact read.
+	 * Locking: read.
 	 */
 	boolean containsContact(T txn, AuthorId a) throws DbException;
 
 	/**
 	 * Returns true if the database contains the given contact.
 	 * <p>
-	 * Locking: contact read.
+	 * Locking: read.
 	 */
 	boolean containsContact(T txn, ContactId c) throws DbException;
 
 	/**
 	 * Returns true if the user subscribes to the given group.
 	 * <p>
-	 * Locking: subscription read.
+	 * Locking: read.
 	 */
 	boolean containsGroup(T txn, GroupId g) throws DbException;
 
 	/**
 	 * Returns true if the database contains the given local pseudonym.
 	 * <p>
-	 * Locking: identity read.
+	 * Locking: read.
 	 */
 	boolean containsLocalAuthor(T txn, AuthorId a) throws DbException;
 
 	/**
 	 * Returns true if the database contains the given message.
 	 * <p>
-	 * Locking: message read.
+	 * Locking: read.
 	 */
 	boolean containsMessage(T txn, MessageId m) throws DbException;
 
 	/**
 	 * Returns true if the database contains the given transport.
 	 * <p>
-	 * Locking: transport read.
+	 * Locking: read.
 	 */
 	boolean containsTransport(T txn, TransportId t) throws DbException;
 
@@ -217,7 +208,7 @@ interface Database<T> {
 	 * Returns true if the user subscribes to the given group and the group is
 	 * visible to the given contact.
 	 * <p>
-	 * Locking: subscription read.
+	 * Locking: read.
 	 */
 	boolean containsVisibleGroup(T txn, ContactId c, GroupId g)
 			throws DbException;
@@ -226,7 +217,7 @@ interface Database<T> {
 	 * Returns true if the database contains the given message and the message
 	 * is visible to the given contact.
 	 * <p>
-	 * Locking: message read, subscription read.
+	 * Locking: read.
 	 */
 	boolean containsVisibleMessage(T txn, ContactId c, MessageId m)
 			throws DbException;
@@ -234,7 +225,7 @@ interface Database<T> {
 	/**
 	 * Returns the number of messages offered by the given contact.
 	 * <p>
-	 * Locking: message read.
+	 * Locking: read.
 	 */
 	int countOfferedMessages(T txn, ContactId c) throws DbException;
 
@@ -242,49 +233,49 @@ interface Database<T> {
 	 * Returns the status of all groups to which the user subscribes or can
 	 * subscribe, excluding inbox groups.
 	 * <p>
-	 * Locking: subscription read.
+	 * Locking: read.
 	 */
 	Collection<GroupStatus> getAvailableGroups(T txn) throws DbException;
 
 	/**
 	 * Returns the configuration for the given transport.
 	 * <p>
-	 * Locking: transport read.
+	 * Locking: read.
 	 */
 	TransportConfig getConfig(T txn, TransportId t) throws DbException;
 
 	/**
 	 * Returns the contact with the given ID.
 	 * <p>
-	 * Locking: contact read.
+	 * Locking: read.
 	 */
 	Contact getContact(T txn, ContactId c) throws DbException;
 
 	/**
 	 * Returns the IDs of all contacts.
 	 * <p>
-	 * Locking: contact read.
+	 * Locking: read.
 	 */
 	Collection<ContactId> getContactIds(T txn) throws DbException;
 
 	/**
 	 * Returns all contacts.
 	 * <p>
-	 * Locking: contact read, window read.
+	 * Locking: read.
 	 */
 	Collection<Contact> getContacts(T txn) throws DbException;
 
 	/**
 	 * Returns all contacts associated with the given local pseudonym.
 	 * <p>
-	 * Locking: contact read.
+	 * Locking: read.
 	 */
 	Collection<ContactId> getContacts(T txn, AuthorId a) throws DbException;
 
 	/**
 	 * Returns all endpoints.
 	 * <p>
-	 * Locking: window read.
+	 * Locking: read.
 	 */
 	Collection<Endpoint> getEndpoints(T txn) throws DbException;
 
@@ -298,14 +289,14 @@ interface Database<T> {
 	/**
 	 * Returns the group with the given ID, if the user subscribes to it.
 	 * <p>
-	 * Locking: subscription read.
+	 * Locking: read.
 	 */
 	Group getGroup(T txn, GroupId g) throws DbException;
 
 	/**
 	 * Returns all groups to which the user subscribes.
 	 * <p>
-	 * Locking: subscription read.
+	 * Locking: read.
 	 */
 	Collection<Group> getGroups(T txn) throws DbException;
 
@@ -313,7 +304,7 @@ interface Database<T> {
 	 * Returns the ID of the inbox group for the given contact, or null if no
 	 * inbox group has been set.
 	 * <p>
-	 * Locking: contact read, subscription read.
+	 * Locking: read.
 	 */
 	GroupId getInboxGroupId(T txn, ContactId c) throws DbException;
 
@@ -321,7 +312,7 @@ interface Database<T> {
 	 * Returns the headers of all messages in the inbox group for the given
 	 * contact, or null if no inbox group has been set.
 	 * <p>
-	 * Locking: contact read, identity read, message read, subscription read.
+	 * Locking: read.
 	 */
 	Collection<MessageHeader> getInboxMessageHeaders(T txn, ContactId c)
 			throws DbException;
@@ -329,21 +320,21 @@ interface Database<T> {
 	/**
 	 * Returns the local pseudonym with the given ID.
 	 * <p>
-	 * Locking: identity read.
+	 * Locking: read.
 	 */
 	LocalAuthor getLocalAuthor(T txn, AuthorId a) throws DbException;
 
 	/**
 	 * Returns all local pseudonyms.
 	 * <p>
-	 * Locking: identity read.
+	 * Locking: read.
 	 */
 	Collection<LocalAuthor> getLocalAuthors(T txn) throws DbException;
 
 	/**
 	 * Returns the local transport properties for all transports.
 	 * <p>
-	 * Locking: transport read.
+	 * Locking: read.
 	 */
 	Map<TransportId, TransportProperties> getLocalProperties(T txn)
 			throws DbException;
@@ -351,7 +342,7 @@ interface Database<T> {
 	/**
 	 * Returns the local transport properties for the given transport.
 	 * <p>
-	 * Locking: transport read.
+	 * Locking: read.
 	 */
 	TransportProperties getLocalProperties(T txn, TransportId t)
 			throws DbException;
@@ -359,14 +350,14 @@ interface Database<T> {
 	/**
 	 * Returns the body of the message identified by the given ID.
 	 * <p>
-	 * Locking: message read.
+	 * Locking: read.
 	 */
 	byte[] getMessageBody(T txn, MessageId m) throws DbException;
 
 	/**
 	 * Returns the headers of all messages in the given group.
 	 * <p>
-	 * Locking: message read.
+	 * Locking: read.
 	 */
 	Collection<MessageHeader> getMessageHeaders(T txn, GroupId g)
 			throws DbException;
@@ -375,7 +366,7 @@ interface Database<T> {
 	 * Returns the IDs of some messages received from the given contact that
 	 * need to be acknowledged, up to the given number of messages.
 	 * <p>
-	 * Locking: message read.
+	 * Locking: read.
 	 */
 	Collection<MessageId> getMessagesToAck(T txn, ContactId c, int maxMessages)
 			throws DbException;
@@ -384,7 +375,7 @@ interface Database<T> {
 	 * Returns the IDs of some messages that are eligible to be offered to the
 	 * given contact, up to the given number of messages.
 	 * <p>
-	 * Locking: message read, subscription read.
+	 * Locking: read.
 	 */
 	Collection<MessageId> getMessagesToOffer(T txn, ContactId c,
 			int maxMessages) throws DbException;
@@ -393,7 +384,7 @@ interface Database<T> {
 	 * Returns the IDs of some messages that are eligible to be sent to the
 	 * given contact, up to the given total length.
 	 * <p>
-	 * Locking: message read, subscription read.
+	 * Locking: read.
 	 */
 	Collection<MessageId> getMessagesToSend(T txn, ContactId c, int maxLength)
 			throws DbException;
@@ -402,7 +393,7 @@ interface Database<T> {
 	 * Returns the IDs of some messages that are eligible to be requested from
 	 * the given contact, up to the given number of messages.
 	 * <p>
-	 * Locking: message read.
+	 * Locking: read.
 	 */
 	Collection<MessageId> getMessagesToRequest(T txn, ContactId c,
 			int maxMessages) throws DbException;
@@ -411,7 +402,7 @@ interface Database<T> {
 	 * Returns the IDs of the oldest messages in the database, with a total
 	 * size less than or equal to the given size.
 	 * <p>
-	 * Locking: message read.
+	 * Locking: read.
 	 */
 	Collection<MessageId> getOldMessages(T txn, int size) throws DbException;
 
@@ -420,28 +411,28 @@ interface Database<T> {
 	 * has no parent, or the parent is absent from the database, or the parent
 	 * belongs to a different group.
 	 * <p>
-	 * Locking: message read.
+	 * Locking: read.
 	 */
 	MessageId getParent(T txn, MessageId m) throws DbException;
 
 	/**
 	 * Returns the message identified by the given ID, in serialised form.
 	 * <p>
-	 * Locking: message read.
+	 * Locking: read.
 	 */
 	byte[] getRawMessage(T txn, MessageId m) throws DbException;
 
 	/**
 	 * Returns true if the given message is marked as read.
 	 * <p>
-	 * Locking: message read.
+	 * Locking: read.
 	 */
 	boolean getReadFlag(T txn, MessageId m) throws DbException;
 
 	/**
 	 * Returns all remote properties for the given transport.
 	 * <p>
-	 * Locking: transport read.
+	 * Locking: read.
 	 */
 	Map<ContactId, TransportProperties> getRemoteProperties(T txn,
 			TransportId t) throws DbException;
@@ -451,7 +442,7 @@ interface Database<T> {
 	 * given contact and have been requested by the contact, up to the given
 	 * total length.
 	 * <p>
-	 * Locking: message read, subscription read.
+	 * Locking: read.
 	 */
 	Collection<MessageId> getRequestedMessagesToSend(T txn, ContactId c,
 			int maxLength) throws DbException;
@@ -459,7 +450,7 @@ interface Database<T> {
 	/**
 	 * Returns a retention ack for the given contact, or null if no ack is due.
 	 * <p>
-	 * Locking: retention write.
+	 * Locking: write.
 	 */
 	RetentionAck getRetentionAck(T txn, ContactId c) throws DbException;
 
@@ -467,7 +458,7 @@ interface Database<T> {
 	 * Returns a retention update for the given contact and updates its expiry
 	 * time using the given latency, or returns null if no update is due.
 	 * <p>
-	 * Locking: message read, retention write.
+	 * Locking: write.
 	 */
 	RetentionUpdate getRetentionUpdate(T txn, ContactId c, long maxLatency)
 			throws DbException;
@@ -475,21 +466,21 @@ interface Database<T> {
 	/**
 	 * Returns all temporary secrets.
 	 * <p>
-	 * Locking: window read.
+	 * Locking: read.
 	 */
 	Collection<TemporarySecret> getSecrets(T txn) throws DbException;
 
 	/**
 	 * Returns all settings.
 	 * <p>
-	 * Locking: setting read.
+	 * Locking: read.
 	 */
 	Settings getSettings(T txn) throws DbException;
 
 	/**
 	 * Returns all contacts who subscribe to the given group.
 	 * <p>
-	 * Locking: subscription read.
+	 * Locking: read.
 	 */
 	Collection<Contact> getSubscribers(T txn, GroupId g) throws DbException;
 
@@ -497,7 +488,7 @@ interface Database<T> {
 	 * Returns a subscription ack for the given contact, or null if no ack is
 	 * due.
 	 * <p>
-	 * Locking: subscription write.
+	 * Locking: write.
 	 */
 	SubscriptionAck getSubscriptionAck(T txn, ContactId c) throws DbException;
 
@@ -505,7 +496,7 @@ interface Database<T> {
 	 * Returns a subscription update for the given contact and updates its
 	 * expiry time using the given latency, or returns null if no update is due.
 	 * <p>
-	 * Locking: subscription write.
+	 * Locking: write.
 	 */
 	SubscriptionUpdate getSubscriptionUpdate(T txn, ContactId c,
 			long maxLatency) throws DbException;
@@ -514,7 +505,7 @@ interface Database<T> {
 	 * Returns a collection of transport acks for the given contact, or null if
 	 * no acks are due.
 	 * <p>
-	 * Locking: transport write.
+	 * Locking: write.
 	 */
 	Collection<TransportAck> getTransportAcks(T txn, ContactId c)
 			throws DbException;
@@ -522,7 +513,7 @@ interface Database<T> {
 	/**
 	 * Returns the maximum latencies of all local transports.
 	 * <p>
-	 * Locking: transport read.
+	 * Locking: read.
 	 */
 	Map<TransportId, Long> getTransportLatencies(T txn) throws DbException;
 
@@ -531,7 +522,7 @@ interface Database<T> {
 	 * updates their expiry times using the given latency, or returns null if
 	 * no updates are due.
 	 * <p>
-	 * Locking: transport write.
+	 * Locking: write.
 	 */
 	Collection<TransportUpdate> getTransportUpdates(T txn, ContactId c,
 			long maxLatency) throws DbException;
@@ -539,14 +530,14 @@ interface Database<T> {
 	/**
 	 * Returns the number of unread messages in each subscribed group.
 	 * <p>
-	 * Locking: message read.
+	 * Locking: read.
 	 */
 	Map<GroupId, Integer> getUnreadMessageCounts(T txn) throws DbException;
 
 	/**
 	 * Returns the IDs of all contacts to which the given group is visible.
 	 * <p>
-	 * Locking: subscription read.
+	 * Locking: read.
 	 */
 	Collection<ContactId> getVisibility(T txn, GroupId g) throws DbException;
 
@@ -555,7 +546,7 @@ interface Database<T> {
 	 * in the given rotation period and returns the old value, or -1 if the
 	 * counter does not exist.
 	 * <p>
-	 * Locking: window write.
+	 * Locking: write.
 	 */
 	long incrementConnectionCounter(T txn, ContactId c, TransportId t,
 			long period) throws DbException;
@@ -564,7 +555,7 @@ interface Database<T> {
 	 * Increments the retention time versions for all contacts to indicate that
 	 * the database's retention time has changed and updates should be sent.
 	 * <p>
-	 * Locking: retention write.
+	 * Locking: write.
 	 */
 	void incrementRetentionVersions(T txn) throws DbException;
 
@@ -572,7 +563,7 @@ interface Database<T> {
 	 * Marks the given messages as not needing to be acknowledged to the
 	 * given contact.
 	 * <p>
-	 * Locking: message write.
+	 * Locking: write.
 	 */
 	void lowerAckFlag(T txn, ContactId c, Collection<MessageId> acked)
 			throws DbException;
@@ -581,7 +572,7 @@ interface Database<T> {
 	 * Marks the given messages as not having been requested by the given
 	 * contact.
 	 * <p>
-	 * Locking: message write.
+	 * Locking: write.
 	 */
 	void lowerRequestedFlag(T txn, ContactId c, Collection<MessageId> requested)
 			throws DbException;
@@ -590,7 +581,7 @@ interface Database<T> {
 	 * Merges the given configuration with the existing configuration for the
 	 * given transport.
 	 * <p>
-	 * Locking: transport write.
+	 * Locking: write.
 	 */
 	void mergeConfig(T txn, TransportId t, TransportConfig config)
 			throws DbException;
@@ -599,7 +590,7 @@ interface Database<T> {
 	 * Merges the given properties with the existing local properties for the
 	 * given transport.
 	 * <p>
-	 * Locking: transport write.
+	 * Locking: write.
 	 */
 	void mergeLocalProperties(T txn, TransportId t, TransportProperties p)
 			throws DbException;
@@ -607,36 +598,35 @@ interface Database<T> {
 	/**
 	 * Merges the given settings with the existing settings.
 	 * <p>
-	 * Locking: setting write.
+	 * Locking: write.
 	 */
 	void mergeSettings(T txn, Settings s) throws DbException;
 
 	/**
 	 * Marks a message as needing to be acknowledged to the given contact.
 	 * <p>
-	 * Locking: message write.
+	 * Locking: write.
 	 */
 	void raiseAckFlag(T txn, ContactId c, MessageId m) throws DbException;
 
 	/**
 	 * Marks a message as having been requested by the given contact.
 	 * <p>
-	 * Locking: message write.
+	 * Locking: write.
 	 */
 	void raiseRequestedFlag(T txn, ContactId c, MessageId m) throws DbException;
 
 	/**
 	 * Marks a message as having been seen by the given contact.
 	 * <p>
-	 * Locking: message write.
+	 * Locking: write.
 	 */
 	void raiseSeenFlag(T txn, ContactId c, MessageId m) throws DbException;
 
 	/**
 	 * Removes a contact from the database.
 	 * <p>
-	 * Locking: contact write, message write, retention write,
-	 * subscription write, transport write, window write.
+	 * Locking: write.
 	 */
 	void removeContact(T txn, ContactId c) throws DbException;
 
@@ -644,7 +634,7 @@ interface Database<T> {
 	 * Unsubscribes from a group. Any messages belonging to the group are
 	 * deleted from the database.
 	 * <p>
-	 * Locking: message write, subscription write.
+	 * Locking: write.
 	 */
 	void removeGroup(T txn, GroupId g) throws DbException;
 
@@ -652,15 +642,14 @@ interface Database<T> {
 	 * Removes a local pseudonym (and all associated contacts) from the
 	 * database.
 	 * <p>
-	 * Locking: contact write, identity write, message write, retention write,
-	 * subscription write, transport write, window write.
+	 * Locking: write.
 	 */
 	void removeLocalAuthor(T txn, AuthorId a) throws DbException;
 
 	/**
 	 * Removes a message (and all associated state) from the database.
 	 * <p>
-	 * Locking: message write.
+	 * Locking: write.
 	 */
 	void removeMessage(T txn, MessageId m) throws DbException;
 
@@ -668,7 +657,7 @@ interface Database<T> {
 	 * Removes an offered message that was offered by the given contact, or
 	 * returns false if there is no such message.
 	 * <p>
-	 * Locking: message write.
+	 * Locking: write.
 	 */
 	boolean removeOfferedMessage(T txn, ContactId c, MessageId m)
 			throws DbException;
@@ -677,7 +666,7 @@ interface Database<T> {
 	 * Removes the given offered messages that were offered by the given
 	 * contact.
 	 * <p>
-	 * Locking: message write.
+	 * Locking: write.
 	 */
 	void removeOfferedMessages(T txn, ContactId c,
 			Collection<MessageId> requested) throws DbException;
@@ -685,14 +674,14 @@ interface Database<T> {
 	/**
 	 * Removes a transport (and all associated state) from the database.
 	 * <p>
-	 * Locking: transport write, window write.
+	 * Locking: write.
 	 */
 	void removeTransport(T txn, TransportId t) throws DbException;
 
 	/**
 	 * Makes a group invisible to the given contact.
 	 * <p>
-	 * Locking: subscription write.
+	 * Locking: write.
 	 */
 	void removeVisibility(T txn, ContactId c, GroupId g) throws DbException;
 
@@ -700,7 +689,7 @@ interface Database<T> {
 	 * Resets the transmission count and expiry time of the given message with
 	 * respect to the given contact.
 	 * <p>
-	 * Locking: message write.
+	 * Locking: write.
 	 */
 	void resetExpiryTime(T txn, ContactId c, MessageId m) throws DbException;
 
@@ -708,7 +697,7 @@ interface Database<T> {
 	 * Sets the connection reordering window for the given endpoint in the
 	 * given rotation period.
 	 * <p>
-	 * Locking: window write.
+	 * Locking: write.
 	 */
 	void setConnectionWindow(T txn, ContactId c, TransportId t, long period,
 			long centre, byte[] bitmap) throws DbException;
@@ -718,7 +707,7 @@ interface Database<T> {
 	 * true, unless an update with an equal or higher version number has
 	 * already been received from the contact.
 	 * <p>
-	 * Locking: message write, subscription write.
+	 * Locking: write.
 	 */
 	boolean setGroups(T txn, ContactId c, Collection<Group> groups,
 			long version) throws DbException;
@@ -727,14 +716,14 @@ interface Database<T> {
 	 * Makes a group visible to the given contact, adds it to the contact's
 	 * subscriptions, and sets it as the inbox group for the contact.
 	 * <p>
-	 * Locking: subscription write.
+	 * Locking: write.
 	 */
 	public void setInboxGroup(T txn, ContactId c, Group g) throws DbException;
 
 	/**
 	 * Marks a message as read or unread.
 	 * <p>
-	 * Locking: message write.
+	 * Locking: write.
 	 */
 	void setReadFlag(T txn, MessageId m, boolean read) throws DbException;
 
@@ -742,7 +731,7 @@ interface Database<T> {
 	 * Sets the remote transport properties for the given contact, replacing
 	 * any existing properties.
 	 * <p>
-	 * Locking: transport write.
+	 * Locking: write.
 	 */
 	void setRemoteProperties(T txn, ContactId c,
 			Map<TransportId, TransportProperties> p) throws DbException;
@@ -753,7 +742,7 @@ interface Database<T> {
 	 * unless an update with an equal or higher version number has already been
 	 * received from the contact.
 	 * <p>
-	 * Locking: transport write.
+	 * Locking: write.
 	 */
 	boolean setRemoteProperties(T txn, ContactId c, TransportId t,
 			TransportProperties p, long version) throws DbException;
@@ -763,7 +752,7 @@ interface Database<T> {
 	 * true, unless an update with an equal or higher version number has
 	 * already been received from the contact.
 	 * <p>
-	 * Locking: retention write.
+	 * Locking: write.
 	 */
 	boolean setRetentionTime(T txn, ContactId c, long retention, long version)
 			throws DbException;
@@ -772,7 +761,7 @@ interface Database<T> {
 	 * Records a retention ack from the given contact for the given version,
 	 * unless the contact has already acked an equal or higher version.
 	 * <p>
-	 * Locking: retention write.
+	 * Locking: write.
 	 */
 	void setRetentionUpdateAcked(T txn, ContactId c, long version)
 			throws DbException;
@@ -781,7 +770,7 @@ interface Database<T> {
 	 * Records a subscription ack from the given contact for the given version,
 	 * unless the contact has already acked an equal or higher version.
 	 * <p>
-	 * Locking: subscription write.
+	 * Locking: write.
 	 */
 	void setSubscriptionUpdateAcked(T txn, ContactId c, long version)
 			throws DbException;
@@ -790,7 +779,7 @@ interface Database<T> {
 	 * Records a transport ack from the give contact for the given version,
 	 * unless the contact has already acked an equal or higher version.
 	 * <p>
-	 * Locking: transport write.
+	 * Locking: write.
 	 */
 	void setTransportUpdateAcked(T txn, ContactId c, TransportId t,
 			long version) throws DbException;
@@ -798,7 +787,7 @@ interface Database<T> {
 	/**
 	 * Makes a group visible or invisible to future contacts by default.
 	 * <p>
-	 * Locking: subscription write.
+	 * Locking: write.
 	 */
 	void setVisibleToAll(T txn, GroupId g, boolean all) throws DbException;
 
@@ -807,7 +796,7 @@ interface Database<T> {
 	 * with respect to the given contact, using the latency of the transport
 	 * over which it was sent.
 	 * <p>
-	 * Locking: message write.
+	 * Locking: write.
 	 */
 	void updateExpiryTime(T txn, ContactId c, MessageId m, long maxLatency)
 			throws DbException;
diff --git a/briar-core/src/org/briarproject/db/DatabaseComponentImpl.java b/briar-core/src/org/briarproject/db/DatabaseComponentImpl.java
index ed245931e0a26690485c8dc1197bad27e30f0f99..79113fbd4371c4a9dd79019bb1be273787b6752e 100644
--- a/briar-core/src/org/briarproject/db/DatabaseComponentImpl.java
+++ b/briar-core/src/org/briarproject/db/DatabaseComponentImpl.java
@@ -93,38 +93,17 @@ DatabaseCleaner.Callback {
 			Logger.getLogger(DatabaseComponentImpl.class.getName());
 	private static final int MS_BETWEEN_SWEEPS = 10 * 1000; // 10 seconds
 
-	/*
-	 * Locks must always be acquired in alphabetical order. See the Database
-	 * interface to find out which calls require which locks.
-	 */
-
-	private final ReentrantReadWriteLock contactLock =
-			new ReentrantReadWriteLock(true);
-	private final ReentrantReadWriteLock identityLock =
-			new ReentrantReadWriteLock(true);
-	private final ReentrantReadWriteLock messageLock =
-			new ReentrantReadWriteLock(true);
-	private final ReentrantReadWriteLock retentionLock =
-			new ReentrantReadWriteLock(true);
-	private final ReentrantReadWriteLock settingLock =
-			new ReentrantReadWriteLock(true);
-	private final ReentrantReadWriteLock subscriptionLock =
-			new ReentrantReadWriteLock(true);
-	private final ReentrantReadWriteLock transportLock =
-			new ReentrantReadWriteLock(true);
-	private final ReentrantReadWriteLock windowLock =
-			new ReentrantReadWriteLock(true);
-
 	private final Database<T> db;
 	private final DatabaseCleaner cleaner;
 	private final ShutdownManager shutdown;
 
+	private final ReentrantReadWriteLock lock =
+			new ReentrantReadWriteLock(true);
 	private final Collection<EventListener> listeners =
 			new CopyOnWriteArrayList<EventListener>();
 
-	private final Object openCloseLock = new Object();
-	private boolean open = false; // Locking: openCloseLock;
-	private int shutdownHandle = -1; // Locking: openCloseLock;
+	private boolean open = false; // Locking: lock.writeLock
+	private int shutdownHandle = -1; // Locking: lock.writeLock
 
 	@Inject
 	DatabaseComponentImpl(Database<T> db, DatabaseCleaner cleaner,
@@ -135,39 +114,47 @@ DatabaseCleaner.Callback {
 	}
 
 	public boolean open() throws DbException, IOException {
-		synchronized(openCloseLock) {
+		Runnable shutdownHook = new Runnable() {
+			public void run() {
+				lock.writeLock().lock();
+				try {
+					shutdownHandle = -1;
+					close();
+				} catch(DbException e) {
+					if(LOG.isLoggable(WARNING))
+						LOG.log(WARNING, e.toString(), e);
+				} catch(IOException e) {
+					if(LOG.isLoggable(WARNING))
+						LOG.log(WARNING, e.toString(), e);
+				} finally {
+					lock.writeLock().unlock();
+				}
+			}
+		};
+		lock.writeLock().lock();
+		try {
 			if(open) throw new IllegalStateException();
 			open = true;
 			boolean reopened = db.open();
 			cleaner.startCleaning(this, MS_BETWEEN_SWEEPS);
-			shutdownHandle = shutdown.addShutdownHook(new Runnable() {
-				public void run() {
-					try {
-						synchronized(openCloseLock) {
-							shutdownHandle = -1;
-							close();
-						}
-					} catch(DbException e) {
-						if(LOG.isLoggable(WARNING))
-							LOG.log(WARNING, e.toString(), e);
-					} catch(IOException e) {
-						if(LOG.isLoggable(WARNING))
-							LOG.log(WARNING, e.toString(), e);
-					}
-				}
-			});
+			shutdownHandle = shutdown.addShutdownHook(shutdownHook);
 			return reopened;
+		} finally {
+			lock.writeLock().unlock();
 		}
 	}
 
 	public void close() throws DbException, IOException {
-		synchronized(openCloseLock) {
+		lock.writeLock().lock();
+		try {
 			if(!open) return;
 			open = false;
 			if(shutdownHandle != -1)
 				shutdown.removeShutdownHook(shutdownHandle);
 			cleaner.stopCleaning();
 			db.close();
+		} finally {
+			lock.writeLock().unlock();
 		}
 	}
 
@@ -179,208 +166,119 @@ DatabaseCleaner.Callback {
 		listeners.remove(l);
 	}
 
+	/** Notifies all listeners of a database event. */
+	private void callListeners(Event e) {
+		for(EventListener l : listeners) l.eventOccurred(e);
+	}
+
 	public ContactId addContact(Author remote, AuthorId local)
 			throws DbException {
 		ContactId c;
-		contactLock.writeLock().lock();
+		lock.writeLock().lock();
 		try {
-			identityLock.readLock().lock();
+			T txn = db.startTransaction();
 			try {
-				messageLock.writeLock().lock();
-				try {
-					retentionLock.writeLock().lock();
-					try {
-						subscriptionLock.writeLock().lock();
-						try {
-							transportLock.writeLock().lock();
-							try {
-								windowLock.writeLock().lock();
-								try {
-									T txn = db.startTransaction();
-									try {
-										if(db.containsContact(txn, remote.getId()))
-											throw new ContactExistsException();
-										if(!db.containsLocalAuthor(txn, local))
-											throw new NoSuchLocalAuthorException();
-										c = db.addContact(txn, remote, local);
-										db.commitTransaction(txn);
-									} catch(DbException e) {
-										db.abortTransaction(txn);
-										throw e;
-									}
-								} finally {
-									windowLock.writeLock().unlock();
-								}
-							} finally {
-								transportLock.writeLock().unlock();
-							}
-						} finally {
-							subscriptionLock.writeLock().unlock();
-						}
-					} finally {
-						retentionLock.writeLock().unlock();
-					}
-				} finally {
-					messageLock.writeLock().unlock();
-				}
-			} finally {
-				identityLock.readLock().unlock();
+				if(db.containsContact(txn, remote.getId()))
+					throw new ContactExistsException();
+				if(!db.containsLocalAuthor(txn, local))
+					throw new NoSuchLocalAuthorException();
+				c = db.addContact(txn, remote, local);
+				db.commitTransaction(txn);
+			} catch(DbException e) {
+				db.abortTransaction(txn);
+				throw e;
 			}
 		} finally {
-			contactLock.writeLock().unlock();
+			lock.writeLock().unlock();
 		}
 		callListeners(new ContactAddedEvent(c));
 		return c;
 	}
 
-	/** Notifies all listeners of a database event. */
-	private void callListeners(Event e) {
-		for(EventListener l : listeners) l.eventOccurred(e);
-	}
-
 	public void addEndpoint(Endpoint ep) throws DbException {
-		contactLock.readLock().lock();
+		lock.writeLock().lock();
 		try {
-			transportLock.readLock().lock();
+			T txn = db.startTransaction();
 			try {
-				windowLock.writeLock().lock();
-				try {
-					T txn = db.startTransaction();
-					try {
-						if(!db.containsContact(txn, ep.getContactId()))
-							throw new NoSuchContactException();
-						if(!db.containsTransport(txn, ep.getTransportId()))
-							throw new NoSuchTransportException();
-						db.addEndpoint(txn, ep);
-						db.commitTransaction(txn);
-					} catch(DbException e) {
-						db.abortTransaction(txn);
-						throw e;
-					}
-				} finally {
-					windowLock.writeLock().unlock();
-				}
-			} finally {
-				transportLock.readLock().unlock();
+				if(!db.containsContact(txn, ep.getContactId()))
+					throw new NoSuchContactException();
+				if(!db.containsTransport(txn, ep.getTransportId()))
+					throw new NoSuchTransportException();
+				db.addEndpoint(txn, ep);
+				db.commitTransaction(txn);
+			} catch(DbException e) {
+				db.abortTransaction(txn);
+				throw e;
 			}
 		} finally {
-			contactLock.readLock().unlock();
+			lock.writeLock().unlock();
 		}
 	}
 
 	public boolean addGroup(Group g) throws DbException {
 		boolean added = false;
-		messageLock.writeLock().lock();
+		lock.writeLock().lock();
 		try {
-			subscriptionLock.writeLock().lock();
+			T txn = db.startTransaction();
 			try {
-				T txn = db.startTransaction();
-				try {
-					if(!db.containsGroup(txn, g.getId()))
-						added = db.addGroup(txn, g);
-					db.commitTransaction(txn);
-				} catch(DbException e) {
-					db.abortTransaction(txn);
-					throw e;
-				}
-			} finally {
-				subscriptionLock.writeLock().unlock();
+				if(!db.containsGroup(txn, g.getId()))
+					added = db.addGroup(txn, g);
+				db.commitTransaction(txn);
+			} catch(DbException e) {
+				db.abortTransaction(txn);
+				throw e;
 			}
 		} finally {
-			messageLock.writeLock().unlock();
+			lock.writeLock().unlock();
 		}
 		if(added) callListeners(new SubscriptionAddedEvent(g));
 		return added;
 	}
 
 	public void addLocalAuthor(LocalAuthor a) throws DbException {
-		contactLock.writeLock().lock();
+		lock.writeLock().lock();
 		try {
-			identityLock.writeLock().lock();
+			T txn = db.startTransaction();
 			try {
-				messageLock.writeLock().lock();
-				try {
-					retentionLock.writeLock().lock();
-					try {
-						subscriptionLock.writeLock().lock();
-						try {
-							transportLock.writeLock().lock();
-							try {
-								windowLock.writeLock().lock();
-								try {
-									T txn = db.startTransaction();
-									try {
-										if(db.containsLocalAuthor(txn, a.getId()))
-											throw new LocalAuthorExistsException();
-										db.addLocalAuthor(txn, a);
-										db.commitTransaction(txn);
-									} catch(DbException e) {
-										db.abortTransaction(txn);
-										throw e;
-									}
-								} finally {
-									windowLock.writeLock().unlock();
-								}
-							} finally {
-								transportLock.writeLock().unlock();
-							}
-						} finally {
-							subscriptionLock.writeLock().unlock();
-						}
-					} finally {
-						retentionLock.writeLock().unlock();
-					}
-				} finally {
-					messageLock.writeLock().unlock();
-				}
-			} finally {
-				identityLock.writeLock().unlock();
+				if(db.containsLocalAuthor(txn, a.getId()))
+					throw new LocalAuthorExistsException();
+				db.addLocalAuthor(txn, a);
+				db.commitTransaction(txn);
+			} catch(DbException e) {
+				db.abortTransaction(txn);
+				throw e;
 			}
 		} finally {
-			contactLock.writeLock().unlock();
+			lock.writeLock().unlock();
 		}
 		callListeners(new LocalAuthorAddedEvent(a.getId()));
 	}
 
 	public void addLocalMessage(Message m) throws DbException {
 		boolean duplicate;
-		contactLock.readLock().lock();
+		lock.writeLock().lock();
 		try {
-			messageLock.writeLock().lock();
+			T txn = db.startTransaction();
 			try {
-				subscriptionLock.readLock().lock();
-				try {
-					T txn = db.startTransaction();
-					try {
-						duplicate = db.containsMessage(txn, m.getId());
-						if(!duplicate) {
-							GroupId g = m.getGroup().getId();
-							if(db.containsGroup(txn, g))
-								addMessage(txn, m, null);
-						}
-						db.commitTransaction(txn);
-					} catch(DbException e) {
-						db.abortTransaction(txn);
-						throw e;
-					}
-				} finally {
-					subscriptionLock.readLock().unlock();
-				}
-			} finally {
-				messageLock.writeLock().unlock();
+				duplicate = db.containsMessage(txn, m.getId());
+				if(!duplicate && db.containsGroup(txn, m.getGroup().getId()))
+					addMessage(txn, m, null);
+				db.commitTransaction(txn);
+			} catch(DbException e) {
+				db.abortTransaction(txn);
+				throw e;
 			}
 		} finally {
-			contactLock.readLock().unlock();
+			lock.writeLock().unlock();
 		}
-		if(!duplicate)
-			callListeners(new MessageAddedEvent(m.getGroup(), null));
+		if(!duplicate) callListeners(new MessageAddedEvent(m.getGroup(), null));
 	}
 
 	/**
 	 * Stores a message, initialises its status with respect to each contact,
 	 * and marks it as read if it was locally generated.
 	 * <p>
-	 * Locking: contact read, message write, subscription read.
+	 * Locking: write.
 	 * @param sender null for a locally generated message.
 	 */
 	private void addMessage(T txn, Message m, ContactId sender)
@@ -408,60 +306,43 @@ DatabaseCleaner.Callback {
 
 	public void addSecrets(Collection<TemporarySecret> secrets)
 			throws DbException {
-		contactLock.readLock().lock();
+		lock.writeLock().lock();
 		try {
-			transportLock.readLock().lock();
+			T txn = db.startTransaction();
 			try {
-				windowLock.writeLock().lock();
-				try {
-					T txn = db.startTransaction();
-					try {
-						Collection<TemporarySecret> relevant =
-								new ArrayList<TemporarySecret>();
-						for(TemporarySecret s : secrets) {
-							ContactId c = s.getContactId();
-							if(!db.containsContact(txn, c)) continue;
-							TransportId t = s.getTransportId();
-							if(!db.containsTransport(txn, t)) continue;
+				Collection<TemporarySecret> relevant =
+						new ArrayList<TemporarySecret>();
+				for(TemporarySecret s : secrets) {
+					if(db.containsContact(txn, s.getContactId()))
+						if(db.containsTransport(txn, s.getTransportId()))
 							relevant.add(s);
-						}
-						if(!secrets.isEmpty()) db.addSecrets(txn, relevant);
-						db.commitTransaction(txn);
-					} catch(DbException e) {
-						db.abortTransaction(txn);
-						throw e;
-					}
-				} finally {
-					windowLock.writeLock().unlock();
 				}
-			} finally {
-				transportLock.readLock().unlock();
+				if(!secrets.isEmpty()) db.addSecrets(txn, relevant);
+				db.commitTransaction(txn);
+			} catch(DbException e) {
+				db.abortTransaction(txn);
+				throw e;
 			}
 		} finally {
-			contactLock.readLock().unlock();
+			lock.writeLock().unlock();
 		}
 	}
 
 	public boolean addTransport(TransportId t, long maxLatency)
 			throws DbException {
 		boolean added;
-		transportLock.writeLock().lock();
+		lock.writeLock().lock();
 		try {
-			windowLock.writeLock().lock();
+			T txn = db.startTransaction();
 			try {
-				T txn = db.startTransaction();
-				try {
-					added = db.addTransport(txn, t, maxLatency);
-					db.commitTransaction(txn);
-				} catch(DbException e) {
-					db.abortTransaction(txn);
-					throw e;
-				}
-			} finally {
-				windowLock.writeLock().unlock();
+				added = db.addTransport(txn, t, maxLatency);
+				db.commitTransaction(txn);
+			} catch(DbException e) {
+				db.abortTransaction(txn);
+				throw e;
 			}
 		} finally {
-			transportLock.writeLock().unlock();
+			lock.writeLock().unlock();
 		}
 		if(added) callListeners(new TransportAddedEvent(t, maxLatency));
 		return added;
@@ -469,26 +350,21 @@ DatabaseCleaner.Callback {
 
 	public Ack generateAck(ContactId c, int maxMessages) throws DbException {
 		Collection<MessageId> ids;
-		contactLock.readLock().lock();
+		lock.writeLock().lock();
 		try {
-			messageLock.writeLock().lock();
+			T txn = db.startTransaction();
 			try {
-				T txn = db.startTransaction();
-				try {
-					if(!db.containsContact(txn, c))
-						throw new NoSuchContactException();
-					ids = db.getMessagesToAck(txn, c, maxMessages);
-					if(!ids.isEmpty()) db.lowerAckFlag(txn, c, ids);
-					db.commitTransaction(txn);
-				} catch(DbException e) {
-					db.abortTransaction(txn);
-					throw e;
-				}
-			} finally {
-				messageLock.writeLock().unlock();
+				if(!db.containsContact(txn, c))
+					throw new NoSuchContactException();
+				ids = db.getMessagesToAck(txn, c, maxMessages);
+				if(!ids.isEmpty()) db.lowerAckFlag(txn, c, ids);
+				db.commitTransaction(txn);
+			} catch(DbException e) {
+				db.abortTransaction(txn);
+				throw e;
 			}
 		} finally {
-			contactLock.readLock().unlock();
+			lock.writeLock().unlock();
 		}
 		if(ids.isEmpty()) return null;
 		return new Ack(ids);
@@ -498,35 +374,25 @@ DatabaseCleaner.Callback {
 			long maxLatency) throws DbException {
 		Collection<MessageId> ids;
 		List<byte[]> messages = new ArrayList<byte[]>();
-		contactLock.readLock().lock();
+		lock.writeLock().lock();
 		try {
-			messageLock.writeLock().lock();
+			T txn = db.startTransaction();
 			try {
-				subscriptionLock.readLock().lock();
-				try {
-					T txn = db.startTransaction();
-					try {
-						if(!db.containsContact(txn, c))
-							throw new NoSuchContactException();
-						ids = db.getMessagesToSend(txn, c, maxLength);
-						for(MessageId m : ids) {
-							messages.add(db.getRawMessage(txn, m));
-							db.updateExpiryTime(txn, c, m, maxLatency);
-						}
-						if(!ids.isEmpty()) db.lowerRequestedFlag(txn, c, ids);
-						db.commitTransaction(txn);
-					} catch(DbException e) {
-						db.abortTransaction(txn);
-						throw e;
-					}
-				} finally {
-					subscriptionLock.readLock().unlock();
+				if(!db.containsContact(txn, c))
+					throw new NoSuchContactException();
+				ids = db.getMessagesToSend(txn, c, maxLength);
+				for(MessageId m : ids) {
+					messages.add(db.getRawMessage(txn, m));
+					db.updateExpiryTime(txn, c, m, maxLatency);
 				}
-			} finally {
-				messageLock.writeLock().unlock();
+				if(!ids.isEmpty()) db.lowerRequestedFlag(txn, c, ids);
+				db.commitTransaction(txn);
+			} catch(DbException e) {
+				db.abortTransaction(txn);
+				throw e;
 			}
 		} finally {
-			contactLock.readLock().unlock();
+			lock.writeLock().unlock();
 		}
 		if(messages.isEmpty()) return null;
 		return Collections.unmodifiableList(messages);
@@ -535,32 +401,22 @@ DatabaseCleaner.Callback {
 	public Offer generateOffer(ContactId c, int maxMessages, long maxLatency)
 			throws DbException {
 		Collection<MessageId> ids;
-		contactLock.readLock().lock();
+		lock.writeLock().lock();
 		try {
-			messageLock.writeLock().lock();
+			T txn = db.startTransaction();
 			try {
-				subscriptionLock.readLock().lock();
-				try {
-					T txn = db.startTransaction();
-					try {
-						if(!db.containsContact(txn, c))
-							throw new NoSuchContactException();
-						ids = db.getMessagesToOffer(txn, c, maxMessages);
-						for(MessageId m : ids)
-							db.updateExpiryTime(txn, c, m, maxLatency);
-						db.commitTransaction(txn);
-					} catch(DbException e) {
-						db.abortTransaction(txn);
-						throw e;
-					}
-				} finally {
-					subscriptionLock.readLock().unlock();
-				}
-			} finally {
-				messageLock.writeLock().unlock();
+				if(!db.containsContact(txn, c))
+					throw new NoSuchContactException();
+				ids = db.getMessagesToOffer(txn, c, maxMessages);
+				for(MessageId m : ids)
+					db.updateExpiryTime(txn, c, m, maxLatency);
+				db.commitTransaction(txn);
+			} catch(DbException e) {
+				db.abortTransaction(txn);
+				throw e;
 			}
 		} finally {
-			contactLock.readLock().unlock();
+			lock.writeLock().unlock();
 		}
 		if(ids.isEmpty()) return null;
 		return new Offer(ids);
@@ -569,26 +425,21 @@ DatabaseCleaner.Callback {
 	public Request generateRequest(ContactId c, int maxMessages)
 			throws DbException {
 		Collection<MessageId> ids;
-		contactLock.readLock().lock();
+		lock.writeLock().lock();
 		try {
-			messageLock.writeLock().lock();
+			T txn = db.startTransaction();
 			try {
-				T txn = db.startTransaction();
-				try {
-					if(!db.containsContact(txn, c))
-						throw new NoSuchContactException();
-					ids = db.getMessagesToRequest(txn, c, maxMessages);
-					if(!ids.isEmpty()) db.removeOfferedMessages(txn, c, ids);
-					db.commitTransaction(txn);
-				} catch(DbException e) {
-					db.abortTransaction(txn);
-					throw e;
-				}
-			} finally {
-				messageLock.writeLock().unlock();
+				if(!db.containsContact(txn, c))
+					throw new NoSuchContactException();
+				ids = db.getMessagesToRequest(txn, c, maxMessages);
+				if(!ids.isEmpty()) db.removeOfferedMessages(txn, c, ids);
+				db.commitTransaction(txn);
+			} catch(DbException e) {
+				db.abortTransaction(txn);
+				throw e;
 			}
 		} finally {
-			contactLock.readLock().unlock();
+			lock.writeLock().unlock();
 		}
 		if(ids.isEmpty()) return null;
 		return new Request(ids);
@@ -598,199 +449,153 @@ DatabaseCleaner.Callback {
 			long maxLatency) throws DbException {
 		Collection<MessageId> ids;
 		List<byte[]> messages = new ArrayList<byte[]>();
-		contactLock.readLock().lock();
+		lock.writeLock().lock();
 		try {
-			messageLock.writeLock().lock();
+			T txn = db.startTransaction();
 			try {
-				subscriptionLock.readLock().lock();
-				try {
-					T txn = db.startTransaction();
-					try {
-						if(!db.containsContact(txn, c))
-							throw new NoSuchContactException();
-						ids = db.getRequestedMessagesToSend(txn, c, maxLength);
-						for(MessageId m : ids) {
-							messages.add(db.getRawMessage(txn, m));
-							db.updateExpiryTime(txn, c, m, maxLatency);
-						}
-						if(!ids.isEmpty()) db.lowerRequestedFlag(txn, c, ids);
-						db.commitTransaction(txn);
-					} catch(DbException e) {
-						db.abortTransaction(txn);
-						throw e;
-					}
-				} finally {
-					subscriptionLock.readLock().unlock();
+				if(!db.containsContact(txn, c))
+					throw new NoSuchContactException();
+				ids = db.getRequestedMessagesToSend(txn, c, maxLength);
+				for(MessageId m : ids) {
+					messages.add(db.getRawMessage(txn, m));
+					db.updateExpiryTime(txn, c, m, maxLatency);
 				}
-			} finally {
-				messageLock.writeLock().unlock();
+				if(!ids.isEmpty()) db.lowerRequestedFlag(txn, c, ids);
+				db.commitTransaction(txn);
+			} catch(DbException e) {
+				db.abortTransaction(txn);
+				throw e;
 			}
 		} finally {
-			contactLock.readLock().unlock();
+			lock.writeLock().unlock();
 		}
 		if(messages.isEmpty()) return null;
 		return Collections.unmodifiableList(messages);
 	}
 
 	public RetentionAck generateRetentionAck(ContactId c) throws DbException {
-		contactLock.readLock().lock();
+		lock.writeLock().lock();
 		try {
-			retentionLock.writeLock().lock();
+			T txn = db.startTransaction();
 			try {
-				T txn = db.startTransaction();
-				try {
-					if(!db.containsContact(txn, c))
-						throw new NoSuchContactException();
-					RetentionAck a = db.getRetentionAck(txn, c);
-					db.commitTransaction(txn);
-					return a;
-				} catch(DbException e) {
-					db.abortTransaction(txn);
-					throw e;
-				}
-			} finally {
-				retentionLock.writeLock().unlock();
+				if(!db.containsContact(txn, c))
+					throw new NoSuchContactException();
+				RetentionAck a = db.getRetentionAck(txn, c);
+				db.commitTransaction(txn);
+				return a;
+			} catch(DbException e) {
+				db.abortTransaction(txn);
+				throw e;
 			}
 		} finally {
-			contactLock.readLock().unlock();
+			lock.writeLock().unlock();
 		}
 	}
 
 	public RetentionUpdate generateRetentionUpdate(ContactId c, long maxLatency)
 			throws DbException {
-		contactLock.readLock().lock();
+		lock.writeLock().lock();
 		try {
-			messageLock.readLock().lock();
+			T txn = db.startTransaction();
 			try {
-				retentionLock.writeLock().lock();
-				try {
-					T txn = db.startTransaction();
-					try {
-						if(!db.containsContact(txn, c))
-							throw new NoSuchContactException();
-						RetentionUpdate u =
-								db.getRetentionUpdate(txn, c, maxLatency);
-						db.commitTransaction(txn);
-						return u;
-					} catch(DbException e) {
-						db.abortTransaction(txn);
-						throw e;
-					}
-				} finally {
-					retentionLock.writeLock().unlock();
-				}
-			} finally {
-				messageLock.readLock().unlock();
+				if(!db.containsContact(txn, c))
+					throw new NoSuchContactException();
+				RetentionUpdate u = db.getRetentionUpdate(txn, c, maxLatency);
+				db.commitTransaction(txn);
+				return u;
+			} catch(DbException e) {
+				db.abortTransaction(txn);
+				throw e;
 			}
 		} finally {
-			contactLock.readLock().unlock();
+			lock.writeLock().unlock();
 		}
 	}
 
 	public SubscriptionAck generateSubscriptionAck(ContactId c)
 			throws DbException {
-		contactLock.readLock().lock();
+		lock.writeLock().lock();
 		try {
-			subscriptionLock.writeLock().lock();
+			T txn = db.startTransaction();
 			try {
-				T txn = db.startTransaction();
-				try {
-					if(!db.containsContact(txn, c))
-						throw new NoSuchContactException();
-					SubscriptionAck a = db.getSubscriptionAck(txn, c);
-					db.commitTransaction(txn);
-					return a;
-				} catch(DbException e) {
-					db.abortTransaction(txn);
-					throw e;
-				}
-			} finally {
-				subscriptionLock.writeLock().unlock();
+				if(!db.containsContact(txn, c))
+					throw new NoSuchContactException();
+				SubscriptionAck a = db.getSubscriptionAck(txn, c);
+				db.commitTransaction(txn);
+				return a;
+			} catch(DbException e) {
+				db.abortTransaction(txn);
+				throw e;
 			}
 		} finally {
-			contactLock.readLock().unlock();
+			lock.writeLock().unlock();
 		}
 	}
 
 	public SubscriptionUpdate generateSubscriptionUpdate(ContactId c,
 			long maxLatency) throws DbException {
-		contactLock.readLock().lock();
+		lock.writeLock().lock();
 		try {
-			subscriptionLock.writeLock().lock();
+			T txn = db.startTransaction();
 			try {
-				T txn = db.startTransaction();
-				try {
-					if(!db.containsContact(txn, c))
-						throw new NoSuchContactException();
-					SubscriptionUpdate u =
-							db.getSubscriptionUpdate(txn, c, maxLatency);
-					db.commitTransaction(txn);
-					return u;
-				} catch(DbException e) {
-					db.abortTransaction(txn);
-					throw e;
-				}
-			} finally {
-				subscriptionLock.writeLock().unlock();
+				if(!db.containsContact(txn, c))
+					throw new NoSuchContactException();
+				SubscriptionUpdate u =
+						db.getSubscriptionUpdate(txn, c, maxLatency);
+				db.commitTransaction(txn);
+				return u;
+			} catch(DbException e) {
+				db.abortTransaction(txn);
+				throw e;
 			}
 		} finally {
-			contactLock.readLock().unlock();
+			lock.writeLock().unlock();
 		}
 	}
 
 	public Collection<TransportAck> generateTransportAcks(ContactId c)
 			throws DbException {
-		contactLock.readLock().lock();
+		lock.writeLock().lock();
 		try {
-			transportLock.writeLock().lock();
+			T txn = db.startTransaction();
 			try {
-				T txn = db.startTransaction();
-				try {
-					if(!db.containsContact(txn, c))
-						throw new NoSuchContactException();
-					Collection<TransportAck> acks = db.getTransportAcks(txn, c);
-					db.commitTransaction(txn);
-					return acks;
-				} catch(DbException e) {
-					db.abortTransaction(txn);
-					throw e;
-				}
-			} finally {
-				transportLock.writeLock().unlock();
+				if(!db.containsContact(txn, c))
+					throw new NoSuchContactException();
+				Collection<TransportAck> acks = db.getTransportAcks(txn, c);
+				db.commitTransaction(txn);
+				return acks;
+			} catch(DbException e) {
+				db.abortTransaction(txn);
+				throw e;
 			}
 		} finally {
-			contactLock.readLock().unlock();
+			lock.writeLock().unlock();
 		}
 	}
 
 	public Collection<TransportUpdate> generateTransportUpdates(ContactId c,
 			long maxLatency) throws DbException {
-		contactLock.readLock().lock();
+		lock.writeLock().lock();
 		try {
-			transportLock.writeLock().lock();
+			T txn = db.startTransaction();
 			try {
-				T txn = db.startTransaction();
-				try {
-					if(!db.containsContact(txn, c))
-						throw new NoSuchContactException();
-					Collection<TransportUpdate> updates =
-							db.getTransportUpdates(txn, c, maxLatency);
-					db.commitTransaction(txn);
-					return updates;
-				} catch(DbException e) {
-					db.abortTransaction(txn);
-					throw e;
-				}
-			} finally {
-				transportLock.writeLock().unlock();
+				if(!db.containsContact(txn, c))
+					throw new NoSuchContactException();
+				Collection<TransportUpdate> updates =
+						db.getTransportUpdates(txn, c, maxLatency);
+				db.commitTransaction(txn);
+				return updates;
+			} catch(DbException e) {
+				db.abortTransaction(txn);
+				throw e;
 			}
 		} finally {
-			contactLock.readLock().unlock();
+			lock.writeLock().unlock();
 		}
 	}
 
 	public Collection<GroupStatus> getAvailableGroups() throws DbException {
-		subscriptionLock.readLock().lock();
+		lock.readLock().lock();
 		try {
 			T txn = db.startTransaction();
 			try {
@@ -802,12 +607,12 @@ DatabaseCleaner.Callback {
 				throw e;
 			}
 		} finally {
-			subscriptionLock.readLock().unlock();
+			lock.readLock().unlock();
 		}
 	}
 
 	public TransportConfig getConfig(TransportId t) throws DbException {
-		transportLock.readLock().lock();
+		lock.readLock().lock();
 		try {
 			T txn = db.startTransaction();
 			try {
@@ -821,12 +626,12 @@ DatabaseCleaner.Callback {
 				throw e;
 			}
 		} finally {
-			transportLock.readLock().unlock();
+			lock.readLock().unlock();
 		}
 	}
 
 	public Contact getContact(ContactId c) throws DbException {
-		contactLock.readLock().lock();
+		lock.readLock().lock();
 		try {
 			T txn = db.startTransaction();
 			try {
@@ -840,34 +645,29 @@ DatabaseCleaner.Callback {
 				throw e;
 			}
 		} finally {
-			contactLock.readLock().unlock();
+			lock.readLock().unlock();
 		}
 	}
 
 	public Collection<Contact> getContacts() throws DbException {
-		contactLock.readLock().lock();
+		lock.readLock().lock();
 		try {
-			windowLock.readLock().lock();
+			T txn = db.startTransaction();
 			try {
-				T txn = db.startTransaction();
-				try {
-					Collection<Contact> contacts = db.getContacts(txn);
-					db.commitTransaction(txn);
-					return contacts;
-				} catch(DbException e) {
-					db.abortTransaction(txn);
-					throw e;
-				}
-			} finally {
-				windowLock.readLock().unlock();
+				Collection<Contact> contacts = db.getContacts(txn);
+				db.commitTransaction(txn);
+				return contacts;
+			} catch(DbException e) {
+				db.abortTransaction(txn);
+				throw e;
 			}
 		} finally {
-			contactLock.readLock().unlock();
+			lock.readLock().unlock();
 		}
 	}
 
 	public Group getGroup(GroupId g) throws DbException {
-		subscriptionLock.readLock().lock();
+		lock.readLock().lock();
 		try {
 			T txn = db.startTransaction();
 			try {
@@ -881,12 +681,12 @@ DatabaseCleaner.Callback {
 				throw e;
 			}
 		} finally {
-			subscriptionLock.readLock().unlock();
+			lock.readLock().unlock();
 		}
 	}
 
 	public Collection<Group> getGroups() throws DbException {
-		subscriptionLock.readLock().lock();
+		lock.readLock().lock();
 		try {
 			T txn = db.startTransaction();
 			try {
@@ -898,72 +698,52 @@ DatabaseCleaner.Callback {
 				throw e;
 			}
 		} finally {
-			subscriptionLock.readLock().unlock();
+			lock.readLock().unlock();
 		}
 	}
 
 	public GroupId getInboxGroupId(ContactId c) throws DbException {
-		contactLock.readLock().lock();
+		lock.readLock().lock();
 		try {
-			subscriptionLock.readLock().lock();
+			T txn = db.startTransaction();
 			try {
-				T txn = db.startTransaction();
-				try {
-					if(!db.containsContact(txn, c))
-						throw new NoSuchContactException();
-					GroupId inbox = db.getInboxGroupId(txn, c);
-					db.commitTransaction(txn);
-					return inbox;
-				} catch(DbException e) {
-					db.abortTransaction(txn);
-					throw e;
-				}
-			} finally {
-				subscriptionLock.readLock().unlock();
+				if(!db.containsContact(txn, c))
+					throw new NoSuchContactException();
+				GroupId inbox = db.getInboxGroupId(txn, c);
+				db.commitTransaction(txn);
+				return inbox;
+			} catch(DbException e) {
+				db.abortTransaction(txn);
+				throw e;
 			}
 		} finally {
-			contactLock.readLock().unlock();
+			lock.readLock().unlock();
 		}
 	}
 
 	public Collection<MessageHeader> getInboxMessageHeaders(ContactId c)
 			throws DbException {
-		contactLock.readLock().lock();
+		lock.readLock().lock();
 		try {
-			identityLock.readLock().lock();
+			T txn = db.startTransaction();
 			try {
-				messageLock.readLock().lock();
-				try {
-					subscriptionLock.readLock().lock();
-					try {
-						T txn = db.startTransaction();
-						try {
-							if(!db.containsContact(txn, c))
-								throw new NoSuchContactException();
-							Collection<MessageHeader> headers =
-									db.getInboxMessageHeaders(txn, c);
-							db.commitTransaction(txn);
-							return headers;
-						} catch(DbException e) {
-							db.abortTransaction(txn);
-							throw e;
-						}
-					} finally {
-						subscriptionLock.readLock().unlock();
-					}
-				} finally {
-					messageLock.readLock().unlock();
-				}
-			} finally {
-				identityLock.readLock().unlock();
+				if(!db.containsContact(txn, c))
+					throw new NoSuchContactException();
+				Collection<MessageHeader> headers =
+						db.getInboxMessageHeaders(txn, c);
+				db.commitTransaction(txn);
+				return headers;
+			} catch(DbException e) {
+				db.abortTransaction(txn);
+				throw e;
 			}
 		} finally {
-			contactLock.readLock().unlock();
+			lock.readLock().unlock();
 		}
 	}
 
 	public LocalAuthor getLocalAuthor(AuthorId a) throws DbException {
-		identityLock.readLock().lock();
+		lock.readLock().lock();
 		try {
 			T txn = db.startTransaction();
 			try {
@@ -977,12 +757,12 @@ DatabaseCleaner.Callback {
 				throw e;
 			}
 		} finally {
-			identityLock.readLock().unlock();
+			lock.readLock().unlock();
 		}
 	}
 
 	public Collection<LocalAuthor> getLocalAuthors() throws DbException {
-		identityLock.readLock().lock();
+		lock.readLock().lock();
 		try {
 			T txn = db.startTransaction();
 			try {
@@ -994,13 +774,13 @@ DatabaseCleaner.Callback {
 				throw e;
 			}
 		} finally {
-			identityLock.readLock().unlock();
+			lock.readLock().unlock();
 		}
 	}
 
 	public Map<TransportId, TransportProperties> getLocalProperties()
 			throws DbException {
-		transportLock.readLock().lock();
+		lock.readLock().lock();
 		try {
 			T txn = db.startTransaction();
 			try {
@@ -1013,13 +793,13 @@ DatabaseCleaner.Callback {
 				throw e;
 			}
 		} finally {
-			transportLock.readLock().unlock();
+			lock.readLock().unlock();
 		}
 	}
 
 	public TransportProperties getLocalProperties(TransportId t)
 			throws DbException {
-		transportLock.readLock().lock();
+		lock.readLock().lock();
 		try {
 			T txn = db.startTransaction();
 			try {
@@ -1033,12 +813,12 @@ DatabaseCleaner.Callback {
 				throw e;
 			}
 		} finally {
-			transportLock.readLock().unlock();
+			lock.readLock().unlock();
 		}
 	}
 
 	public byte[] getMessageBody(MessageId m) throws DbException {
-		messageLock.readLock().lock();
+		lock.readLock().lock();
 		try {
 			T txn = db.startTransaction();
 			try {
@@ -1052,38 +832,33 @@ DatabaseCleaner.Callback {
 				throw e;
 			}
 		} finally {
-			messageLock.readLock().unlock();
+			lock.readLock().unlock();
 		}
 	}
 
 	public Collection<MessageHeader> getMessageHeaders(GroupId g)
 			throws DbException {
-		messageLock.readLock().lock();
+		lock.readLock().lock();
 		try {
-			subscriptionLock.readLock().lock();
+			T txn = db.startTransaction();
 			try {
-				T txn = db.startTransaction();
-				try {
-					if(!db.containsGroup(txn, g))
-						throw new NoSuchSubscriptionException();
-					Collection<MessageHeader> headers =
-							db.getMessageHeaders(txn, g);
-					db.commitTransaction(txn);
-					return headers;
-				} catch(DbException e) {
-					db.abortTransaction(txn);
-					throw e;
-				}
-			} finally {
-				subscriptionLock.readLock().unlock();
+				if(!db.containsGroup(txn, g))
+					throw new NoSuchSubscriptionException();
+				Collection<MessageHeader> headers =
+						db.getMessageHeaders(txn, g);
+				db.commitTransaction(txn);
+				return headers;
+			} catch(DbException e) {
+				db.abortTransaction(txn);
+				throw e;
 			}
 		} finally {
-			messageLock.readLock().unlock();
+			lock.readLock().unlock();
 		}
 	}
 
 	public boolean getReadFlag(MessageId m) throws DbException {
-		messageLock.readLock().lock();
+		lock.readLock().lock();
 		try {
 			T txn = db.startTransaction();
 			try {
@@ -1097,13 +872,13 @@ DatabaseCleaner.Callback {
 				throw e;
 			}
 		} finally {
-			messageLock.readLock().unlock();
+			lock.readLock().unlock();
 		}
 	}
 
 	public Map<ContactId, TransportProperties> getRemoteProperties(
 			TransportId t) throws DbException {
-		transportLock.readLock().lock();
+		lock.readLock().lock();
 		try {
 			T txn = db.startTransaction();
 			try {
@@ -1116,12 +891,12 @@ DatabaseCleaner.Callback {
 				throw e;
 			}
 		} finally {
-			transportLock.readLock().unlock();
+			lock.readLock().unlock();
 		}
 	}
 
 	public Collection<TemporarySecret> getSecrets() throws DbException {
-		windowLock.readLock().lock();
+		lock.readLock().lock();
 		try {
 			T txn = db.startTransaction();
 			try {
@@ -1133,12 +908,12 @@ DatabaseCleaner.Callback {
 				throw e;
 			}
 		} finally {
-			windowLock.readLock().unlock();
+			lock.readLock().unlock();
 		}
 	}
 
 	public Settings getSettings() throws DbException {
-		settingLock.readLock().lock();
+		lock.readLock().lock();
 		try {
 			T txn = db.startTransaction();
 			try {
@@ -1150,12 +925,12 @@ DatabaseCleaner.Callback {
 				throw e;
 			}
 		} finally {
-			settingLock.readLock().unlock();
+			lock.readLock().unlock();
 		}
 	}
 
 	public Collection<Contact> getSubscribers(GroupId g) throws DbException {
-		subscriptionLock.readLock().lock();
+		lock.readLock().lock();
 		try {
 			T txn = db.startTransaction();
 			try {
@@ -1167,12 +942,12 @@ DatabaseCleaner.Callback {
 				throw e;
 			}
 		} finally {
-			subscriptionLock.readLock().unlock();
+			lock.readLock().unlock();
 		}
 	}
 
 	public Map<TransportId, Long> getTransportLatencies() throws DbException {
-		transportLock.readLock().lock();
+		lock.readLock().lock();
 		try {
 			T txn = db.startTransaction();
 			try {
@@ -1185,12 +960,12 @@ DatabaseCleaner.Callback {
 				throw e;
 			}
 		} finally {
-			transportLock.readLock().unlock();
+			lock.readLock().unlock();
 		}
 	}
 
 	public Map<GroupId, Integer> getUnreadMessageCounts() throws DbException {
-		messageLock.readLock().lock();
+		lock.readLock().lock();
 		try {
 			T txn = db.startTransaction();
 			try {
@@ -1202,12 +977,12 @@ DatabaseCleaner.Callback {
 				throw e;
 			}
 		} finally {
-			messageLock.readLock().unlock();
+			lock.readLock().unlock();
 		}
 	}
 
 	public Collection<ContactId> getVisibility(GroupId g) throws DbException {
-		subscriptionLock.readLock().lock();
+		lock.readLock().lock();
 		try {
 			T txn = db.startTransaction();
 			try {
@@ -1221,46 +996,35 @@ DatabaseCleaner.Callback {
 				throw e;
 			}
 		} finally {
-			subscriptionLock.readLock().unlock();
+			lock.readLock().unlock();
 		}
 	}
 
 	public long incrementConnectionCounter(ContactId c, TransportId t,
 			long period) throws DbException {
-		contactLock.readLock().lock();
+		lock.writeLock().lock();
 		try {
-			transportLock.readLock().lock();
+			T txn = db.startTransaction();
 			try {
-				windowLock.writeLock().lock();
-				try {
-					T txn = db.startTransaction();
-					try {
-						if(!db.containsContact(txn, c))
-							throw new NoSuchContactException();
-						if(!db.containsTransport(txn, t))
-							throw new NoSuchTransportException();
-						long counter = db.incrementConnectionCounter(txn, c, t,
-								period);
-						db.commitTransaction(txn);
-						return counter;
-					} catch(DbException e) {
-						db.abortTransaction(txn);
-						throw e;
-					}
-				} finally {
-					windowLock.writeLock().unlock();
-				}
-			} finally {
-				transportLock.readLock().unlock();
+				if(!db.containsContact(txn, c))
+					throw new NoSuchContactException();
+				if(!db.containsTransport(txn, t))
+					throw new NoSuchTransportException();
+				long counter = db.incrementConnectionCounter(txn, c, t, period);
+				db.commitTransaction(txn);
+				return counter;
+			} catch(DbException e) {
+				db.abortTransaction(txn);
+				throw e;
 			}
 		} finally {
-			contactLock.readLock().unlock();
+			lock.writeLock().unlock();
 		}
 	}
 
 	public void mergeConfig(TransportId t, TransportConfig c)
 			throws DbException {
-		transportLock.writeLock().lock();
+		lock.writeLock().lock();
 		try {
 			T txn = db.startTransaction();
 			try {
@@ -1273,14 +1037,14 @@ DatabaseCleaner.Callback {
 				throw e;
 			}
 		} finally {
-			transportLock.writeLock().unlock();
+			lock.writeLock().unlock();
 		}
 	}
 
 	public void mergeLocalProperties(TransportId t, TransportProperties p)
 			throws DbException {
 		boolean changed = false;
-		transportLock.writeLock().lock();
+		lock.writeLock().lock();
 		try {
 			T txn = db.startTransaction();
 			try {
@@ -1296,14 +1060,14 @@ DatabaseCleaner.Callback {
 				throw e;
 			}
 		} finally {
-			transportLock.writeLock().unlock();
+			lock.writeLock().unlock();
 		}
 		if(changed) callListeners(new LocalTransportsUpdatedEvent());
 	}
 
 	public void mergeSettings(Settings s) throws DbException {
 		boolean changed = false;
-		settingLock.writeLock().lock();
+		lock.writeLock().lock();
 		try {
 			T txn = db.startTransaction();
 			try {
@@ -1317,113 +1081,92 @@ DatabaseCleaner.Callback {
 				throw e;
 			}
 		} finally {
-			settingLock.writeLock().unlock();
+			lock.writeLock().unlock();
 		}
 		if(changed) callListeners(new SettingsUpdatedEvent());
 	}
 
 	public void receiveAck(ContactId c, Ack a) throws DbException {
 		Collection<MessageId> acked = new ArrayList<MessageId>();
-		contactLock.readLock().lock();
+		lock.writeLock().lock();
 		try {
-			messageLock.writeLock().lock();
+			T txn = db.startTransaction();
 			try {
-				T txn = db.startTransaction();
-				try {
-					if(!db.containsContact(txn, c))
-						throw new NoSuchContactException();
-					for(MessageId m : a.getMessageIds()) {
-						if(db.containsVisibleMessage(txn, c, m)) {
-							db.raiseSeenFlag(txn, c, m);
-							acked.add(m);
-						}
+				if(!db.containsContact(txn, c))
+					throw new NoSuchContactException();
+				for(MessageId m : a.getMessageIds()) {
+					if(db.containsVisibleMessage(txn, c, m)) {
+						db.raiseSeenFlag(txn, c, m);
+						acked.add(m);
 					}
-					db.commitTransaction(txn);
-				} catch(DbException e) {
-					db.abortTransaction(txn);
-					throw e;
 				}
-			} finally {
-				messageLock.writeLock().unlock();
+				db.commitTransaction(txn);
+			} catch(DbException e) {
+				db.abortTransaction(txn);
+				throw e;
 			}
 		} finally {
-			contactLock.readLock().unlock();
+			lock.writeLock().unlock();
 		}
 		callListeners(new MessagesAckedEvent(c, acked));
 	}
 
 	public void receiveMessage(ContactId c, Message m) throws DbException {
 		boolean duplicate, visible;
-		contactLock.readLock().lock();
+		lock.writeLock().lock();
 		try {
-			messageLock.writeLock().lock();
+			T txn = db.startTransaction();
 			try {
-				subscriptionLock.readLock().lock();
-				try {
-					T txn = db.startTransaction();
-					try {
-						if(!db.containsContact(txn, c))
-							throw new NoSuchContactException();
-						duplicate = db.containsMessage(txn, m.getId());
-						GroupId g = m.getGroup().getId();
-						visible = db.containsVisibleGroup(txn, c, g);
-						if(!duplicate && visible) addMessage(txn, m, c);
-						if(visible) db.raiseAckFlag(txn, c, m.getId());
-						db.commitTransaction(txn);
-					} catch(DbException e) {
-						db.abortTransaction(txn);
-						throw e;
-					}
-				} finally {
-					subscriptionLock.readLock().unlock();
+				if(!db.containsContact(txn, c))
+					throw new NoSuchContactException();
+				duplicate = db.containsMessage(txn, m.getId());
+				visible = db.containsVisibleGroup(txn, c, m.getGroup().getId());
+				if(visible) {
+					if(!duplicate) addMessage(txn, m, c);
+					db.raiseAckFlag(txn, c, m.getId());
 				}
-			} finally {
-				messageLock.writeLock().unlock();
+				db.commitTransaction(txn);
+			} catch(DbException e) {
+				db.abortTransaction(txn);
+				throw e;
 			}
 		} finally {
-			contactLock.readLock().unlock();
+			lock.writeLock().unlock();
+		}
+		if(visible) {
+			if(!duplicate)
+				callListeners(new MessageAddedEvent(m.getGroup(), c));
+			callListeners(new MessageToAckEvent(c));
 		}
-		if(visible) callListeners(new MessageToAckEvent(c));
-		if(!duplicate) callListeners(new MessageAddedEvent(m.getGroup(), c));
 	}
 
 	public void receiveOffer(ContactId c, Offer o) throws DbException {
 		boolean ack = false, request = false;
-		contactLock.readLock().lock();
+		lock.writeLock().lock();
 		try {
-			messageLock.writeLock().lock();
+			T txn = db.startTransaction();
 			try {
-				subscriptionLock.readLock().lock();
-				try {
-					T txn = db.startTransaction();
-					try {
-						if(!db.containsContact(txn, c))
-							throw new NoSuchContactException();
-						int count = db.countOfferedMessages(txn, c);
-						for(MessageId m : o.getMessageIds()) {
-							if(db.containsVisibleMessage(txn, c, m)) {
-								db.raiseSeenFlag(txn, c, m);
-								db.raiseAckFlag(txn, c, m);
-								ack = true;
-							} else if(count < MAX_OFFERED_MESSAGES) {
-								db.addOfferedMessage(txn, c, m);
-								request = true;
-								count++;
-							}
-						}
-						db.commitTransaction(txn);
-					} catch(DbException e) {
-						db.abortTransaction(txn);
-						throw e;
+				if(!db.containsContact(txn, c))
+					throw new NoSuchContactException();
+				int count = db.countOfferedMessages(txn, c);
+				for(MessageId m : o.getMessageIds()) {
+					if(db.containsVisibleMessage(txn, c, m)) {
+						db.raiseSeenFlag(txn, c, m);
+						db.raiseAckFlag(txn, c, m);
+						ack = true;
+					} else if(count < MAX_OFFERED_MESSAGES) {
+						db.addOfferedMessage(txn, c, m);
+						request = true;
+						count++;
 					}
-				} finally {
-					subscriptionLock.readLock().unlock();
 				}
-			} finally {
-				messageLock.writeLock().unlock();
+				db.commitTransaction(txn);
+			} catch(DbException e) {
+				db.abortTransaction(txn);
+				throw e;
 			}
 		} finally {
-			contactLock.readLock().unlock();
+			lock.writeLock().unlock();
 		}
 		if(ack) callListeners(new MessageToAckEvent(c));
 		if(request) callListeners(new MessageToRequestEvent(c));
@@ -1431,269 +1174,196 @@ DatabaseCleaner.Callback {
 
 	public void receiveRequest(ContactId c, Request r) throws DbException {
 		boolean requested = false;
-		contactLock.readLock().lock();
+		lock.writeLock().lock();
 		try {
-			messageLock.writeLock().lock();
+			T txn = db.startTransaction();
 			try {
-				T txn = db.startTransaction();
-				try {
-					if(!db.containsContact(txn, c))
-						throw new NoSuchContactException();
-					for(MessageId m : r.getMessageIds()) {
-						if(db.containsVisibleMessage(txn, c, m)) {
-							db.raiseRequestedFlag(txn, c, m);
-							db.resetExpiryTime(txn, c, m);
-							requested = true;
-						}
+				if(!db.containsContact(txn, c))
+					throw new NoSuchContactException();
+				for(MessageId m : r.getMessageIds()) {
+					if(db.containsVisibleMessage(txn, c, m)) {
+						db.raiseRequestedFlag(txn, c, m);
+						db.resetExpiryTime(txn, c, m);
+						requested = true;
 					}
-					db.commitTransaction(txn);
-				} catch(DbException e) {
-					db.abortTransaction(txn);
-					throw e;
 				}
-			} finally {
-				messageLock.writeLock().unlock();
+				db.commitTransaction(txn);
+			} catch(DbException e) {
+				db.abortTransaction(txn);
+				throw e;
 			}
 		} finally {
-			contactLock.readLock().unlock();
+			lock.writeLock().unlock();
 		}
 		if(requested) callListeners(new MessageRequestedEvent(c));
 	}
 
 	public void receiveRetentionAck(ContactId c, RetentionAck a)
 			throws DbException {
-		contactLock.readLock().lock();
+		lock.writeLock().lock();
 		try {
-			retentionLock.writeLock().lock();
+			T txn = db.startTransaction();
 			try {
-				T txn = db.startTransaction();
-				try {
-					if(!db.containsContact(txn, c))
-						throw new NoSuchContactException();
-					db.setRetentionUpdateAcked(txn, c, a.getVersion());
-					db.commitTransaction(txn);
-				} catch(DbException e) {
-					db.abortTransaction(txn);
-					throw e;
-				}
-			} finally {
-				retentionLock.writeLock().unlock();
+				if(!db.containsContact(txn, c))
+					throw new NoSuchContactException();
+				db.setRetentionUpdateAcked(txn, c, a.getVersion());
+				db.commitTransaction(txn);
+			} catch(DbException e) {
+				db.abortTransaction(txn);
+				throw e;
 			}
 		} finally {
-			contactLock.readLock().unlock();
+			lock.writeLock().unlock();
 		}
 	}
 
 	public void receiveRetentionUpdate(ContactId c, RetentionUpdate u)
 			throws DbException {
 		boolean updated;
-		contactLock.readLock().lock();
+		lock.writeLock().lock();
 		try {
-			retentionLock.writeLock().lock();
+			T txn = db.startTransaction();
 			try {
-				T txn = db.startTransaction();
-				try {
-					if(!db.containsContact(txn, c))
-						throw new NoSuchContactException();
-					updated = db.setRetentionTime(txn, c, u.getRetentionTime(),
-							u.getVersion());
-					db.commitTransaction(txn);
-				} catch(DbException e) {
-					db.abortTransaction(txn);
-					throw e;
-				}
-			} finally {
-				retentionLock.writeLock().unlock();
+				if(!db.containsContact(txn, c))
+					throw new NoSuchContactException();
+				long retention = u.getRetentionTime(), version = u.getVersion();
+				updated = db.setRetentionTime(txn, c, retention, version);
+				db.commitTransaction(txn);
+			} catch(DbException e) {
+				db.abortTransaction(txn);
+				throw e;
 			}
 		} finally {
-			contactLock.readLock().unlock();
+			lock.writeLock().unlock();
 		}
 		if(updated) callListeners(new RemoteRetentionTimeUpdatedEvent(c));
 	}
 
 	public void receiveSubscriptionAck(ContactId c, SubscriptionAck a)
 			throws DbException {
-		contactLock.readLock().lock();
+		lock.writeLock().lock();
 		try {
-			subscriptionLock.writeLock().lock();
+			T txn = db.startTransaction();
 			try {
-				T txn = db.startTransaction();
-				try {
-					if(!db.containsContact(txn, c))
-						throw new NoSuchContactException();
-					db.setSubscriptionUpdateAcked(txn, c, a.getVersion());
-					db.commitTransaction(txn);
-				} catch(DbException e) {
-					db.abortTransaction(txn);
-					throw e;
-				}
-			} finally {
-				subscriptionLock.writeLock().unlock();
+				if(!db.containsContact(txn, c))
+					throw new NoSuchContactException();
+				db.setSubscriptionUpdateAcked(txn, c, a.getVersion());
+				db.commitTransaction(txn);
+			} catch(DbException e) {
+				db.abortTransaction(txn);
+				throw e;
 			}
 		} finally {
-			contactLock.readLock().unlock();
+			lock.writeLock().unlock();
 		}
 	}
 
 	public void receiveSubscriptionUpdate(ContactId c, SubscriptionUpdate u)
 			throws DbException {
 		boolean updated;
-		contactLock.readLock().lock();
+		lock.writeLock().lock();
 		try {
-			messageLock.writeLock().lock();
+			T txn = db.startTransaction();
 			try {
-				subscriptionLock.writeLock().lock();
-				try {
-					T txn = db.startTransaction();
-					try {
-						if(!db.containsContact(txn, c))
-							throw new NoSuchContactException();
-						Collection<Group> groups = u.getGroups();
-						long version = u.getVersion();
-						updated = db.setGroups(txn, c, groups, version);
-						db.commitTransaction(txn);
-					} catch(DbException e) {
-						db.abortTransaction(txn);
-						throw e;
-					}
-				} finally {
-					subscriptionLock.writeLock().unlock();
-				}
-			} finally {
-				messageLock.writeLock().unlock();
+				if(!db.containsContact(txn, c))
+					throw new NoSuchContactException();
+				updated = db.setGroups(txn, c, u.getGroups(), u.getVersion());
+				db.commitTransaction(txn);
+			} catch(DbException e) {
+				db.abortTransaction(txn);
+				throw e;
 			}
 		} finally {
-			contactLock.readLock().unlock();
+			lock.writeLock().unlock();
 		}
 		if(updated) callListeners(new RemoteSubscriptionsUpdatedEvent(c));
 	}
 
 	public void receiveTransportAck(ContactId c, TransportAck a)
 			throws DbException {
-		contactLock.readLock().lock();
+		lock.writeLock().lock();
 		try {
-			transportLock.writeLock().lock();
+			T txn = db.startTransaction();
 			try {
-				T txn = db.startTransaction();
-				try {
-					if(!db.containsContact(txn, c))
-						throw new NoSuchContactException();
-					TransportId t = a.getId();
-					if(!db.containsTransport(txn, t))
-						throw new NoSuchTransportException();
-					db.setTransportUpdateAcked(txn, c, t, a.getVersion());
-					db.commitTransaction(txn);
-				} catch(DbException e) {
-					db.abortTransaction(txn);
-					throw e;
-				}
-			} finally {
-				transportLock.writeLock().unlock();
+				if(!db.containsContact(txn, c))
+					throw new NoSuchContactException();
+				if(!db.containsTransport(txn, a.getId()))
+					throw new NoSuchTransportException();
+				db.setTransportUpdateAcked(txn, c, a.getId(), a.getVersion());
+				db.commitTransaction(txn);
+			} catch(DbException e) {
+				db.abortTransaction(txn);
+				throw e;
 			}
 		} finally {
-			contactLock.readLock().unlock();
+			lock.writeLock().unlock();
 		}
 	}
 
 	public void receiveTransportUpdate(ContactId c, TransportUpdate u)
 			throws DbException {
 		boolean updated;
-		contactLock.readLock().lock();
+		lock.writeLock().lock();
 		try {
-			transportLock.writeLock().lock();
+			T txn = db.startTransaction();
 			try {
-				T txn = db.startTransaction();
-				try {
-					if(!db.containsContact(txn, c))
-						throw new NoSuchContactException();
-					TransportId t = u.getId();
-					TransportProperties p = u.getProperties();
-					long version = u.getVersion();
-					updated = db.setRemoteProperties(txn, c, t, p, version);
-					db.commitTransaction(txn);
-				} catch(DbException e) {
-					db.abortTransaction(txn);
-					throw e;
-				}
-			} finally {
-				transportLock.writeLock().unlock();
+				if(!db.containsContact(txn, c))
+					throw new NoSuchContactException();
+				TransportId t = u.getId();
+				TransportProperties p = u.getProperties();
+				long version = u.getVersion();
+				updated = db.setRemoteProperties(txn, c, t, p, version);
+				db.commitTransaction(txn);
+			} catch(DbException e) {
+				db.abortTransaction(txn);
+				throw e;
 			}
 		} finally {
-			contactLock.readLock().unlock();
+			lock.writeLock().unlock();
 		}
 		if(updated)
 			callListeners(new RemoteTransportsUpdatedEvent(c, u.getId()));
 	}
 
 	public void removeContact(ContactId c) throws DbException {
-		contactLock.writeLock().lock();
+		lock.writeLock().lock();
 		try {
-			messageLock.writeLock().lock();
+			T txn = db.startTransaction();
 			try {
-				retentionLock.writeLock().lock();
-				try {
-					subscriptionLock.writeLock().lock();
-					try {
-						transportLock.writeLock().lock();
-						try {
-							windowLock.writeLock().lock();
-							try {
-								T txn = db.startTransaction();
-								try {
-									if(!db.containsContact(txn, c))
-										throw new NoSuchContactException();
-									GroupId g = db.getInboxGroupId(txn, c);
-									if(g != null) db.removeGroup(txn, g);
-									db.removeContact(txn, c);
-									db.commitTransaction(txn);
-								} catch(DbException e) {
-									db.abortTransaction(txn);
-									throw e;
-								}
-							} finally {
-								windowLock.writeLock().unlock();
-							}
-						} finally {
-							transportLock.writeLock().unlock();
-						}
-					} finally {
-						subscriptionLock.writeLock().unlock();
-					}
-				} finally {
-					retentionLock.writeLock().unlock();
-				}
-			} finally {
-				messageLock.writeLock().unlock();
+				if(!db.containsContact(txn, c))
+					throw new NoSuchContactException();
+				GroupId g = db.getInboxGroupId(txn, c);
+				if(g != null) db.removeGroup(txn, g);
+				db.removeContact(txn, c);
+				db.commitTransaction(txn);
+			} catch(DbException e) {
+				db.abortTransaction(txn);
+				throw e;
 			}
 		} finally {
-			contactLock.writeLock().unlock();
+			lock.writeLock().unlock();
 		}
 		callListeners(new ContactRemovedEvent(c));
 	}
 
 	public void removeGroup(Group g) throws DbException {
 		Collection<ContactId> affected;
-		messageLock.writeLock().lock();
+		lock.writeLock().lock();
 		try {
-			subscriptionLock.writeLock().lock();
+			T txn = db.startTransaction();
 			try {
-				T txn = db.startTransaction();
-				try {
-					GroupId id = g.getId();
-					if(!db.containsGroup(txn, id))
-						throw new NoSuchSubscriptionException();
-					affected = db.getVisibility(txn, id);
-					db.removeGroup(txn, id);
-					db.commitTransaction(txn);
-				} catch(DbException e) {
-					db.abortTransaction(txn);
-					throw e;
-				}
-			} finally {
-				subscriptionLock.writeLock().unlock();
+				GroupId id = g.getId();
+				if(!db.containsGroup(txn, id))
+					throw new NoSuchSubscriptionException();
+				affected = db.getVisibility(txn, id);
+				db.removeGroup(txn, id);
+				db.commitTransaction(txn);
+			} catch(DbException e) {
+				db.abortTransaction(txn);
+				throw e;
 			}
 		} finally {
-			messageLock.writeLock().unlock();
+			lock.writeLock().unlock();
 		}
 		callListeners(new SubscriptionRemovedEvent(g));
 		callListeners(new LocalSubscriptionsUpdatedEvent(affected));
@@ -1701,141 +1371,90 @@ DatabaseCleaner.Callback {
 
 	public void removeLocalAuthor(AuthorId a) throws DbException {
 		Collection<ContactId> affected;
-		contactLock.writeLock().lock();
+		lock.writeLock().lock();
 		try {
-			identityLock.writeLock().lock();
+			T txn = db.startTransaction();
 			try {
-				messageLock.writeLock().lock();
-				try {
-					retentionLock.writeLock().lock();
-					try {
-						subscriptionLock.writeLock().lock();
-						try {
-							transportLock.writeLock().lock();
-							try {
-								windowLock.writeLock().lock();
-								try {
-									T txn = db.startTransaction();
-									try {
-										if(!db.containsLocalAuthor(txn, a))
-											throw new NoSuchLocalAuthorException();
-										affected = db.getContacts(txn, a);
-										for(ContactId c : affected) {
-											GroupId g = db.getInboxGroupId(txn, c);
-											if(g != null) db.removeGroup(txn, g);
-										}
-										db.removeLocalAuthor(txn, a);
-										db.commitTransaction(txn);
-									} catch(DbException e) {
-										db.abortTransaction(txn);
-										throw e;
-									}
-								} finally {
-									windowLock.writeLock().unlock();
-								}
-							} finally {
-								transportLock.writeLock().unlock();
-							}
-						} finally {
-							subscriptionLock.writeLock().unlock();
-						}
-					} finally {
-						retentionLock.writeLock().unlock();
-					}
-				} finally {
-					messageLock.writeLock().unlock();
+				if(!db.containsLocalAuthor(txn, a))
+					throw new NoSuchLocalAuthorException();
+				affected = db.getContacts(txn, a);
+				for(ContactId c : affected) {
+					GroupId g = db.getInboxGroupId(txn, c);
+					if(g != null) db.removeGroup(txn, g);
 				}
-			} finally {
-				identityLock.writeLock().unlock();
+				db.removeLocalAuthor(txn, a);
+				db.commitTransaction(txn);
+			} catch(DbException e) {
+				db.abortTransaction(txn);
+				throw e;
 			}
 		} finally {
-			contactLock.writeLock().unlock();
+			lock.writeLock().unlock();
 		}
 		for(ContactId c : affected) callListeners(new ContactRemovedEvent(c));
 		callListeners(new LocalAuthorRemovedEvent(a));
 	}
 
 	public void removeTransport(TransportId t) throws DbException {
-		transportLock.writeLock().lock();
+		lock.writeLock().lock();
 		try {
-			windowLock.writeLock().lock();
+			T txn = db.startTransaction();
 			try {
-				T txn = db.startTransaction();
-				try {
-					if(!db.containsTransport(txn, t))
-						throw new NoSuchTransportException();
-					db.removeTransport(txn, t);
-					db.commitTransaction(txn);
-				} catch(DbException e) {
-					db.abortTransaction(txn);
-					throw e;
-				}
-			} finally {
-				windowLock.writeLock().unlock();
+				if(!db.containsTransport(txn, t))
+					throw new NoSuchTransportException();
+				db.removeTransport(txn, t);
+				db.commitTransaction(txn);
+			} catch(DbException e) {
+				db.abortTransaction(txn);
+				throw e;
 			}
 		} finally {
-			transportLock.writeLock().unlock();
+			lock.writeLock().unlock();
 		}
 		callListeners(new TransportRemovedEvent(t));
 	}
 
 	public void setConnectionWindow(ContactId c, TransportId t, long period,
 			long centre, byte[] bitmap) throws DbException {
-		contactLock.readLock().lock();
+		lock.writeLock().lock();
 		try {
-			transportLock.readLock().lock();
+			T txn = db.startTransaction();
 			try {
-				windowLock.writeLock().lock();
-				try {
-					T txn = db.startTransaction();
-					try {
-						if(!db.containsContact(txn, c))
-							throw new NoSuchContactException();
-						if(!db.containsTransport(txn, t))
-							throw new NoSuchTransportException();
-						db.setConnectionWindow(txn, c, t, period, centre,
-								bitmap);
-						db.commitTransaction(txn);
-					} catch(DbException e) {
-						db.abortTransaction(txn);
-						throw e;
-					}
-				} finally {
-					windowLock.writeLock().unlock();
-				}
-			} finally {
-				transportLock.readLock().unlock();
+				if(!db.containsContact(txn, c))
+					throw new NoSuchContactException();
+				if(!db.containsTransport(txn, t))
+					throw new NoSuchTransportException();
+				db.setConnectionWindow(txn, c, t, period, centre, bitmap);
+				db.commitTransaction(txn);
+			} catch(DbException e) {
+				db.abortTransaction(txn);
+				throw e;
 			}
 		} finally {
-			contactLock.readLock().unlock();
+			lock.writeLock().unlock();
 		}
 	}
 
 	public void setInboxGroup(ContactId c, Group g) throws DbException {
-		contactLock.readLock().lock();
+		lock.writeLock().lock();
 		try {
-			subscriptionLock.writeLock().lock();
+			T txn = db.startTransaction();
 			try {
-				T txn = db.startTransaction();
-				try {
-					if(!db.containsContact(txn, c))
-						throw new NoSuchContactException();
-					db.setInboxGroup(txn, c, g);
-					db.commitTransaction(txn);
-				} catch(DbException e) {
-					db.abortTransaction(txn);
-					throw e;
-				}
-			} finally {
-				subscriptionLock.writeLock().unlock();
+				if(!db.containsContact(txn, c))
+					throw new NoSuchContactException();
+				db.setInboxGroup(txn, c, g);
+				db.commitTransaction(txn);
+			} catch(DbException e) {
+				db.abortTransaction(txn);
+				throw e;
 			}
 		} finally {
-			contactLock.readLock().unlock();
+			lock.writeLock().unlock();
 		}
 	}
 
 	public void setReadFlag(MessageId m, boolean read) throws DbException {
-		messageLock.writeLock().lock();
+		lock.writeLock().lock();
 		try {
 			T txn = db.startTransaction();
 			try {
@@ -1848,73 +1467,63 @@ DatabaseCleaner.Callback {
 				throw e;
 			}
 		} finally {
-			messageLock.writeLock().unlock();
+			lock.writeLock().unlock();
 		}
 	}
 
 	public void setRemoteProperties(ContactId c,
 			Map<TransportId, TransportProperties> p) throws DbException {
-		contactLock.readLock().lock();
+		lock.writeLock().lock();
 		try {
-			transportLock.writeLock().lock();
+			T txn = db.startTransaction();
 			try {
-				T txn = db.startTransaction();
-				try {
-					if(!db.containsContact(txn, c))
-						throw new NoSuchContactException();
-					db.setRemoteProperties(txn, c, p);
-					db.commitTransaction(txn);
-				} catch(DbException e) {
-					db.abortTransaction(txn);
-					throw e;
-				}
-			} finally {
-				transportLock.writeLock().unlock();
+				if(!db.containsContact(txn, c))
+					throw new NoSuchContactException();
+				db.setRemoteProperties(txn, c, p);
+				db.commitTransaction(txn);
+			} catch(DbException e) {
+				db.abortTransaction(txn);
+				throw e;
 			}
 		} finally {
-			contactLock.readLock().unlock();
+			lock.writeLock().unlock();
 		}
 	}
 
 	public void setVisibility(GroupId g, Collection<ContactId> visible)
 			throws DbException {
 		Collection<ContactId> affected = new ArrayList<ContactId>();
-		contactLock.readLock().lock();
+		lock.writeLock().lock();
 		try {
-			subscriptionLock.writeLock().lock();
+			T txn = db.startTransaction();
 			try {
-				T txn = db.startTransaction();
-				try {
-					if(!db.containsGroup(txn, g))
-						throw new NoSuchSubscriptionException();
-					// Use HashSets for O(1) lookups, O(n) overall running time
-					HashSet<ContactId> now = new HashSet<ContactId>(visible);
-					Collection<ContactId> before = db.getVisibility(txn, g);
-					before = new HashSet<ContactId>(before);
-					// Set the group's visibility for each current contact
-					for(ContactId c : db.getContactIds(txn)) {
-						boolean wasBefore = before.contains(c);
-						boolean isNow = now.contains(c);
-						if(!wasBefore && isNow) {
-							db.addVisibility(txn, c, g);
-							affected.add(c);
-						} else if(wasBefore && !isNow) {
-							db.removeVisibility(txn, c, g);
-							affected.add(c);
-						}
+				if(!db.containsGroup(txn, g))
+					throw new NoSuchSubscriptionException();
+				// Use HashSets for O(1) lookups, O(n) overall running time
+				HashSet<ContactId> now = new HashSet<ContactId>(visible);
+				Collection<ContactId> before = db.getVisibility(txn, g);
+				before = new HashSet<ContactId>(before);
+				// Set the group's visibility for each current contact
+				for(ContactId c : db.getContactIds(txn)) {
+					boolean wasBefore = before.contains(c);
+					boolean isNow = now.contains(c);
+					if(!wasBefore && isNow) {
+						db.addVisibility(txn, c, g);
+						affected.add(c);
+					} else if(wasBefore && !isNow) {
+						db.removeVisibility(txn, c, g);
+						affected.add(c);
 					}
-					// Make the group invisible to future contacts
-					db.setVisibleToAll(txn, g, false);
-					db.commitTransaction(txn);
-				} catch(DbException e) {
-					db.abortTransaction(txn);
-					throw e;
 				}
-			} finally {
-				subscriptionLock.writeLock().unlock();
+				// Make the group invisible to future contacts
+				db.setVisibleToAll(txn, g, false);
+				db.commitTransaction(txn);
+			} catch(DbException e) {
+				db.abortTransaction(txn);
+				throw e;
 			}
 		} finally {
-			contactLock.readLock().unlock();
+			lock.writeLock().unlock();
 		}
 		if(!affected.isEmpty())
 			callListeners(new LocalSubscriptionsUpdatedEvent(affected));
@@ -1922,37 +1531,32 @@ DatabaseCleaner.Callback {
 
 	public void setVisibleToAll(GroupId g, boolean all) throws DbException {
 		Collection<ContactId> affected = new ArrayList<ContactId>();
-		contactLock.readLock().lock();
+		lock.writeLock().lock();
 		try {
-			subscriptionLock.writeLock().lock();
+			T txn = db.startTransaction();
 			try {
-				T txn = db.startTransaction();
-				try {
-					if(!db.containsGroup(txn, g))
-						throw new NoSuchSubscriptionException();
-					// Make the group visible or invisible to future contacts
-					db.setVisibleToAll(txn, g, all);
-					if(all) {
-						// Make the group visible to all current contacts
-						Collection<ContactId> before = db.getVisibility(txn, g);
-						before = new HashSet<ContactId>(before);
-						for(ContactId c : db.getContactIds(txn)) {
-							if(!before.contains(c)) {
-								db.addVisibility(txn, c, g);
-								affected.add(c);
-							}
+				if(!db.containsGroup(txn, g))
+					throw new NoSuchSubscriptionException();
+				// Make the group visible or invisible to future contacts
+				db.setVisibleToAll(txn, g, all);
+				if(all) {
+					// Make the group visible to all current contacts
+					Collection<ContactId> before = db.getVisibility(txn, g);
+					before = new HashSet<ContactId>(before);
+					for(ContactId c : db.getContactIds(txn)) {
+						if(!before.contains(c)) {
+							db.addVisibility(txn, c, g);
+							affected.add(c);
 						}
 					}
-					db.commitTransaction(txn);
-				} catch(DbException e) {
-					db.abortTransaction(txn);
-					throw e;
 				}
-			} finally {
-				subscriptionLock.writeLock().unlock();
+				db.commitTransaction(txn);
+			} catch(DbException e) {
+				db.abortTransaction(txn);
+				throw e;
 			}
 		} finally {
-			contactLock.readLock().unlock();
+			lock.writeLock().unlock();
 		}
 		if(!affected.isEmpty())
 			callListeners(new LocalSubscriptionsUpdatedEvent(affected));
@@ -1979,29 +1583,24 @@ DatabaseCleaner.Callback {
 	 */
 	private boolean expireMessages(int size) throws DbException {
 		Collection<MessageId> expired;
-		messageLock.writeLock().lock();
+		lock.writeLock().lock();
 		try {
-			retentionLock.writeLock().lock();
+			T txn = db.startTransaction();
 			try {
-				T txn = db.startTransaction();
-				try {
-					expired = db.getOldMessages(txn, size);
-					if(!expired.isEmpty()) {
-						for(MessageId m : expired) db.removeMessage(txn, m);
-						db.incrementRetentionVersions(txn);
-						if(LOG.isLoggable(INFO))
-							LOG.info("Expired " + expired.size() + " messages");
-					}
-					db.commitTransaction(txn);
-				} catch(DbException e) {
-					db.abortTransaction(txn);
-					throw e;
+				expired = db.getOldMessages(txn, size);
+				if(!expired.isEmpty()) {
+					for(MessageId m : expired) db.removeMessage(txn, m);
+					db.incrementRetentionVersions(txn);
+					if(LOG.isLoggable(INFO))
+						LOG.info("Expired " + expired.size() + " messages");
 				}
-			} finally {
-				retentionLock.writeLock().unlock();
+				db.commitTransaction(txn);
+			} catch(DbException e) {
+				db.abortTransaction(txn);
+				throw e;
 			}
 		} finally {
-			messageLock.writeLock().unlock();
+			lock.writeLock().unlock();
 		}
 		if(expired.isEmpty()) return false;
 		callListeners(new MessageExpiredEvent());
diff --git a/briar-core/src/org/briarproject/db/DatabaseModule.java b/briar-core/src/org/briarproject/db/DatabaseModule.java
index 10edb618afd4c590e67689a52f3c6d0c699fbd2b..11719acb602aeb80263927e876558a543c50d044 100644
--- a/briar-core/src/org/briarproject/db/DatabaseModule.java
+++ b/briar-core/src/org/briarproject/db/DatabaseModule.java
@@ -25,9 +25,6 @@ import com.google.inject.Provides;
 
 public class DatabaseModule extends AbstractModule {
 
-	/** The maximum number of executor threads. */
-	private static final int MAX_EXECUTOR_THREADS = 10;
-
 	private final ExecutorService databaseExecutor;
 
 	public DatabaseModule() {
@@ -36,9 +33,9 @@ public class DatabaseModule extends AbstractModule {
 		// Discard tasks that are submitted during shutdown
 		RejectedExecutionHandler policy =
 				new ThreadPoolExecutor.DiscardPolicy();
-		// Create a limited # of threads and keep them in the pool for 60 secs
-		databaseExecutor = new ThreadPoolExecutor(0, MAX_EXECUTOR_THREADS,
-				60, SECONDS, queue, policy);
+		// Use a single thread and keep it in the pool for 60 secs
+		databaseExecutor = new ThreadPoolExecutor(0, 1, 60, SECONDS, queue,
+				policy);
 	}
 
 	protected void configure() {
diff --git a/briar-core/src/org/briarproject/db/JdbcDatabase.java b/briar-core/src/org/briarproject/db/JdbcDatabase.java
index 42214788e855005b03aae50fb4eddb0f58212ca9..5d51ad10491df44a9e907ea1093f4849d17863c9 100644
--- a/briar-core/src/org/briarproject/db/JdbcDatabase.java
+++ b/briar-core/src/org/briarproject/db/JdbcDatabase.java
@@ -71,8 +71,6 @@ abstract class JdbcDatabase implements Database<Connection> {
 					+ " value VARCHAR NOT NULL,"
 					+ " PRIMARY KEY (key))";
 
-	// Locking: identity
-	// Dependents: contact, message, retention, subscription, transport, window
 	private static final String CREATE_LOCAL_AUTHORS =
 			"CREATE TABLE localAuthors"
 					+ " (authorId HASH NOT NULL,"
@@ -82,8 +80,6 @@ abstract class JdbcDatabase implements Database<Connection> {
 					+ " created BIGINT NOT NULL,"
 					+ " PRIMARY KEY (authorId))";
 
-	// Locking: contact
-	// Dependents: message, retention, subscription, transport, window
 	private static final String CREATE_CONTACTS =
 			"CREATE TABLE contacts"
 					+ " (contactId COUNTER,"
@@ -97,8 +93,6 @@ abstract class JdbcDatabase implements Database<Connection> {
 					+ " REFERENCES localAuthors (authorId)"
 					+ " ON DELETE CASCADE)";
 
-	// Locking: subscription
-	// Dependents: message
 	private static final String CREATE_GROUPS =
 			"CREATE TABLE groups"
 					+ " (groupId HASH NOT NULL,"
@@ -107,7 +101,6 @@ abstract class JdbcDatabase implements Database<Connection> {
 					+ " visibleToAll BOOLEAN NOT NULL,"
 					+ " PRIMARY KEY (groupId))";
 
-	// Locking: subscription
 	private static final String CREATE_GROUP_VISIBILITIES =
 			"CREATE TABLE groupVisibilities"
 					+ " (contactId INT NOT NULL,"
@@ -121,7 +114,6 @@ abstract class JdbcDatabase implements Database<Connection> {
 					+ " REFERENCES groups (groupId)"
 					+ " ON DELETE CASCADE)";
 
-	// Locking: subscription
 	private static final String CREATE_CONTACT_GROUPS =
 			"CREATE TABLE contactGroups"
 					+ " (contactId INT NOT NULL,"
@@ -133,7 +125,6 @@ abstract class JdbcDatabase implements Database<Connection> {
 					+ " REFERENCES contacts (contactId)"
 					+ " ON DELETE CASCADE)";
 
-	// Locking: subscription
 	private static final String CREATE_GROUP_VERSIONS =
 			"CREATE TABLE groupVersions"
 					+ " (contactId INT NOT NULL,"
@@ -148,7 +139,6 @@ abstract class JdbcDatabase implements Database<Connection> {
 					+ " REFERENCES contacts (contactId)"
 					+ " ON DELETE CASCADE)";
 
-	// Locking: message
 	private static final String CREATE_MESSAGES =
 			"CREATE TABLE messages"
 					+ " (messageId HASH NOT NULL,"
@@ -182,7 +172,6 @@ abstract class JdbcDatabase implements Database<Connection> {
 					+ " REFERENCES contacts (contactId)"
 					+ " ON DELETE CASCADE)";
 
-	// Locking: message
 	private static final String CREATE_STATUSES =
 			"CREATE TABLE statuses"
 					+ " (messageId HASH NOT NULL,"
@@ -206,7 +195,6 @@ abstract class JdbcDatabase implements Database<Connection> {
 	private static final String INDEX_STATUSES_BY_CONTACT =
 			"CREATE INDEX statusesByContact ON statuses (contactId)";
 
-	// Locking: retention
 	private static final String CREATE_RETENTION_VERSIONS =
 			"CREATE TABLE retentionVersions"
 					+ " (contactId INT NOT NULL,"
@@ -222,15 +210,12 @@ abstract class JdbcDatabase implements Database<Connection> {
 					+ " REFERENCES contacts (contactId)"
 					+ " ON DELETE CASCADE)";
 
-	// Locking: transport
-	// Dependents: window
 	private static final String CREATE_TRANSPORTS =
 			"CREATE TABLE transports"
 					+ " (transportId VARCHAR NOT NULL,"
 					+ " maxLatency BIGINT NOT NULL,"
 					+ " PRIMARY KEY (transportId))";
 
-	// Locking: transport
 	private static final String CREATE_TRANSPORT_CONFIGS =
 			"CREATE TABLE transportConfigs"
 					+ " (transportId VARCHAR NOT NULL,"
@@ -241,7 +226,6 @@ abstract class JdbcDatabase implements Database<Connection> {
 					+ " REFERENCES transports (transportId)"
 					+ " ON DELETE CASCADE)";
 
-	// Locking: transport
 	private static final String CREATE_TRANSPORT_PROPS =
 			"CREATE TABLE transportProperties"
 					+ " (transportId VARCHAR NOT NULL,"
@@ -252,7 +236,6 @@ abstract class JdbcDatabase implements Database<Connection> {
 					+ " REFERENCES transports (transportId)"
 					+ " ON DELETE CASCADE)";
 
-	// Locking: transport
 	private static final String CREATE_TRANSPORT_VERSIONS =
 			"CREATE TABLE transportVersions"
 					+ " (contactId INT NOT NULL,"
@@ -269,7 +252,6 @@ abstract class JdbcDatabase implements Database<Connection> {
 					+ " REFERENCES transports (transportId)"
 					+ " ON DELETE CASCADE)";
 
-	// Locking: transport
 	private static final String CREATE_CONTACT_TRANSPORT_PROPS =
 			"CREATE TABLE contactTransportProperties"
 					+ " (contactId INT NOT NULL,"
@@ -281,7 +263,6 @@ abstract class JdbcDatabase implements Database<Connection> {
 					+ " REFERENCES contacts (contactId)"
 					+ " ON DELETE CASCADE)";
 
-	// Locking: transport
 	private static final String CREATE_CONTACT_TRANSPORT_VERSIONS =
 			"CREATE TABLE contactTransportVersions"
 					+ " (contactId INT NOT NULL,"
@@ -293,7 +274,6 @@ abstract class JdbcDatabase implements Database<Connection> {
 					+ " REFERENCES contacts (contactId)"
 					+ " ON DELETE CASCADE)";
 
-	// Locking: window
 	private static final String CREATE_ENDPOINTS =
 			"CREATE TABLE endpoints"
 					+ " (contactId INT NOT NULL,"
@@ -308,7 +288,6 @@ abstract class JdbcDatabase implements Database<Connection> {
 					+ " REFERENCES transports (transportId)"
 					+ " ON DELETE CASCADE)";
 
-	// Locking: window
 	private static final String CREATE_SECRETS =
 			"CREATE TABLE secrets"
 					+ " (contactId INT NOT NULL,"
@@ -1098,8 +1077,8 @@ abstract class JdbcDatabase implements Database<Connection> {
 		}
 	}
 
-	public boolean containsVisibleGroup(Connection txn, ContactId c,
-			GroupId g) throws DbException {
+	public boolean containsVisibleGroup(Connection txn, ContactId c, GroupId g)
+			throws DbException {
 		PreparedStatement ps = null;
 		ResultSet rs = null;
 		try {
diff --git a/briar-core/src/org/briarproject/lifecycle/LifecycleModule.java b/briar-core/src/org/briarproject/lifecycle/LifecycleModule.java
index 8c6c7360f218b4c4d5478cd43615831c711b7620..0714a0555e4082817fe08b82bc462036616c69f7 100644
--- a/briar-core/src/org/briarproject/lifecycle/LifecycleModule.java
+++ b/briar-core/src/org/briarproject/lifecycle/LifecycleModule.java
@@ -1,18 +1,49 @@
 package org.briarproject.lifecycle;
 
+import static java.util.concurrent.TimeUnit.SECONDS;
+
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.Executor;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.RejectedExecutionHandler;
+import java.util.concurrent.SynchronousQueue;
+import java.util.concurrent.ThreadPoolExecutor;
+
 import javax.inject.Singleton;
 
+import org.briarproject.api.lifecycle.IoExecutor;
 import org.briarproject.api.lifecycle.LifecycleManager;
 import org.briarproject.api.lifecycle.ShutdownManager;
 
 import com.google.inject.AbstractModule;
+import com.google.inject.Provides;
 
 public class LifecycleModule extends AbstractModule {
 
+	private final ExecutorService ioExecutor;
+
+	public LifecycleModule() {
+		// The thread pool is unbounded, so use direct handoff
+		BlockingQueue<Runnable> queue = new SynchronousQueue<Runnable>();
+		// Discard tasks that are submitted during shutdown
+		RejectedExecutionHandler policy =
+				new ThreadPoolExecutor.DiscardPolicy();
+		// Create threads as required and keep them in the pool for 60 seconds
+		ioExecutor = new ThreadPoolExecutor(0, Integer.MAX_VALUE,
+				60, SECONDS, queue, policy);
+	}
+
+	@Override
 	protected void configure() {
 		bind(LifecycleManager.class).to(
 				LifecycleManagerImpl.class).in(Singleton.class);
 		bind(ShutdownManager.class).to(
 				ShutdownManagerImpl.class).in(Singleton.class);
 	}
+
+	@Provides @Singleton @IoExecutor
+	Executor getIoExecutor(LifecycleManager lifecycleManager) {
+		lifecycleManager.registerForShutdown(ioExecutor);
+		return ioExecutor;
+	}
 }
diff --git a/briar-core/src/org/briarproject/messaging/duplex/DuplexConnection.java b/briar-core/src/org/briarproject/messaging/duplex/DuplexConnection.java
index 56ec0aa93db900a8b507c83cab36b1e09a6147e5..de6d569a5ecdc96fa803289fc1e928a9e68711b2 100644
--- a/briar-core/src/org/briarproject/messaging/duplex/DuplexConnection.java
+++ b/briar-core/src/org/briarproject/messaging/duplex/DuplexConnection.java
@@ -276,7 +276,7 @@ abstract class DuplexConnection implements EventListener {
 		}
 	}
 
-	// This task runs on a database thread
+	// This task runs on the database thread
 	private class ReceiveAck implements Runnable {
 
 		private final Ack ack;
@@ -315,7 +315,7 @@ abstract class DuplexConnection implements EventListener {
 		}
 	}
 
-	// This task runs on a database thread
+	// This task runs on the database thread
 	private class ReceiveMessage implements Runnable {
 
 		private final Message message;
@@ -334,7 +334,7 @@ abstract class DuplexConnection implements EventListener {
 		}
 	}
 
-	// This task runs on a database thread
+	// This task runs on the database thread
 	private class ReceiveOffer implements Runnable {
 
 		private final Offer offer;
@@ -353,7 +353,7 @@ abstract class DuplexConnection implements EventListener {
 		}
 	}
 
-	// This task runs on a database thread
+	// This task runs on the database thread
 	private class ReceiveRequest implements Runnable {
 
 		private final Request request;
@@ -372,7 +372,7 @@ abstract class DuplexConnection implements EventListener {
 		}
 	}
 
-	// This task runs on a database thread
+	// This task runs on the database thread
 	private class ReceiveRetentionAck implements Runnable {
 
 		private final RetentionAck ack;
@@ -391,7 +391,7 @@ abstract class DuplexConnection implements EventListener {
 		}
 	}
 
-	// This task runs on a database thread
+	// This task runs on the database thread
 	private class ReceiveRetentionUpdate implements Runnable {
 
 		private final RetentionUpdate update;
@@ -410,7 +410,7 @@ abstract class DuplexConnection implements EventListener {
 		}
 	}
 
-	// This task runs on a database thread
+	// This task runs on the database thread
 	private class ReceiveSubscriptionAck implements Runnable {
 
 		private final SubscriptionAck ack;
@@ -429,7 +429,7 @@ abstract class DuplexConnection implements EventListener {
 		}
 	}
 
-	// This task runs on a database thread
+	// This task runs on the database thread
 	private class ReceiveSubscriptionUpdate implements Runnable {
 
 		private final SubscriptionUpdate update;
@@ -448,7 +448,7 @@ abstract class DuplexConnection implements EventListener {
 		}
 	}
 
-	// This task runs on a database thread
+	// This task runs on the database thread
 	private class ReceiveTransportAck implements Runnable {
 
 		private final TransportAck ack;
@@ -467,7 +467,7 @@ abstract class DuplexConnection implements EventListener {
 		}
 	}
 
-	// This task runs on a database thread
+	// This task runs on the database thread
 	private class ReceiveTransportUpdate implements Runnable {
 
 		private final TransportUpdate update;
@@ -486,7 +486,7 @@ abstract class DuplexConnection implements EventListener {
 		}
 	}
 
-	// This task runs on a database thread
+	// This task runs on the database thread
 	private class GenerateAck implements Runnable {
 
 		public void run() {
@@ -525,7 +525,7 @@ abstract class DuplexConnection implements EventListener {
 		}
 	}
 
-	// This task runs on a database thread
+	// This task runs on the database thread
 	private class GenerateBatch implements Runnable {
 
 		public void run() {
@@ -564,7 +564,7 @@ abstract class DuplexConnection implements EventListener {
 		}
 	}
 
-	// This task runs on a database thread
+	// This task runs on the database thread
 	private class GenerateOffer implements Runnable {
 
 		public void run() {
@@ -603,7 +603,7 @@ abstract class DuplexConnection implements EventListener {
 		}
 	}
 
-	// This task runs on a database thread
+	// This task runs on the database thread
 	private class GenerateRequest implements Runnable {
 
 		public void run() {
@@ -642,7 +642,7 @@ abstract class DuplexConnection implements EventListener {
 		}
 	}
 
-	// This task runs on a database thread
+	// This task runs on the database thread
 	private class GenerateRetentionAck implements Runnable {
 
 		public void run() {
@@ -679,7 +679,7 @@ abstract class DuplexConnection implements EventListener {
 		}
 	}
 
-	// This task runs on a database thread
+	// This task runs on the database thread
 	private class GenerateRetentionUpdate implements Runnable {
 
 		public void run() {
@@ -717,7 +717,7 @@ abstract class DuplexConnection implements EventListener {
 		}
 	}
 
-	// This task runs on a database thread
+	// This task runs on the database thread
 	private class GenerateSubscriptionAck implements Runnable {
 
 		public void run() {
@@ -754,7 +754,7 @@ abstract class DuplexConnection implements EventListener {
 		}
 	}
 
-	// This task runs on a database thread
+	// This task runs on the database thread
 	private class GenerateSubscriptionUpdate implements Runnable {
 
 		public void run() {
@@ -792,7 +792,7 @@ abstract class DuplexConnection implements EventListener {
 		}
 	}
 
-	// This task runs on a database thread
+	// This task runs on the database thread
 	private class GenerateTransportAcks implements Runnable {
 
 		public void run() {
@@ -830,7 +830,7 @@ abstract class DuplexConnection implements EventListener {
 		}
 	}
 
-	// This task runs on a database thread
+	// This task runs on the database thread
 	private class GenerateTransportUpdates implements Runnable {
 
 		public void run() {
diff --git a/briar-core/src/org/briarproject/plugins/PluginManagerImpl.java b/briar-core/src/org/briarproject/plugins/PluginManagerImpl.java
index 3cdfbe9a9cb745e25a8ca9ea56b8442389f40d54..722897476e04e8b08f50421f4f9e3156b70d942d 100644
--- a/briar-core/src/org/briarproject/plugins/PluginManagerImpl.java
+++ b/briar-core/src/org/briarproject/plugins/PluginManagerImpl.java
@@ -23,9 +23,9 @@ import org.briarproject.api.TransportId;
 import org.briarproject.api.TransportProperties;
 import org.briarproject.api.db.DatabaseComponent;
 import org.briarproject.api.db.DbException;
+import org.briarproject.api.lifecycle.IoExecutor;
 import org.briarproject.api.plugins.Plugin;
 import org.briarproject.api.plugins.PluginCallback;
-import org.briarproject.api.plugins.PluginExecutor;
 import org.briarproject.api.plugins.PluginManager;
 import org.briarproject.api.plugins.duplex.DuplexPlugin;
 import org.briarproject.api.plugins.duplex.DuplexPluginCallback;
@@ -49,7 +49,7 @@ class PluginManagerImpl implements PluginManager {
 	private static final Logger LOG =
 			Logger.getLogger(PluginManagerImpl.class.getName());
 
-	private final Executor pluginExecutor;
+	private final Executor ioExecutor;
 	private final SimplexPluginConfig simplexPluginConfig;
 	private final DuplexPluginConfig duplexPluginConfig;
 	private final Clock clock;
@@ -62,12 +62,12 @@ class PluginManagerImpl implements PluginManager {
 	private final List<DuplexPlugin> duplexPlugins;
 
 	@Inject
-	PluginManagerImpl(@PluginExecutor Executor pluginExecutor,
+	PluginManagerImpl(@IoExecutor Executor ioExecutor,
 			SimplexPluginConfig simplexPluginConfig, 
 			DuplexPluginConfig duplexPluginConfig, Clock clock,
 			DatabaseComponent db, Poller poller,
 			ConnectionDispatcher dispatcher, UiCallback uiCallback) {
-		this.pluginExecutor = pluginExecutor;
+		this.ioExecutor = ioExecutor;
 		this.simplexPluginConfig = simplexPluginConfig;
 		this.duplexPluginConfig = duplexPluginConfig;
 		this.clock = clock;
@@ -87,14 +87,14 @@ class PluginManagerImpl implements PluginManager {
 				simplexPluginConfig.getFactories();
 		final CountDownLatch sLatch = new CountDownLatch(sFactories.size());
 		for(SimplexPluginFactory factory : sFactories)
-			pluginExecutor.execute(new SimplexPluginStarter(factory, sLatch));
+			ioExecutor.execute(new SimplexPluginStarter(factory, sLatch));
 		// Instantiate and start the duplex plugins
 		LOG.info("Starting duplex plugins");
 		Collection<DuplexPluginFactory> dFactories =
 				duplexPluginConfig.getFactories();
 		final CountDownLatch dLatch = new CountDownLatch(dFactories.size());
 		for(DuplexPluginFactory factory : dFactories)
-			pluginExecutor.execute(new DuplexPluginStarter(factory, dLatch));
+			ioExecutor.execute(new DuplexPluginStarter(factory, dLatch));
 		// Wait for the plugins to start
 		try {
 			sLatch.await();
@@ -119,11 +119,11 @@ class PluginManagerImpl implements PluginManager {
 		// Stop the simplex plugins
 		LOG.info("Stopping simplex plugins");
 		for(SimplexPlugin plugin : simplexPlugins)
-			pluginExecutor.execute(new PluginStopper(plugin, latch));
+			ioExecutor.execute(new PluginStopper(plugin, latch));
 		// Stop the duplex plugins
 		LOG.info("Stopping duplex plugins");
 		for(DuplexPlugin plugin : duplexPlugins)
-			pluginExecutor.execute(new PluginStopper(plugin, latch));
+			ioExecutor.execute(new PluginStopper(plugin, latch));
 		plugins.clear();
 		simplexPlugins.clear();
 		duplexPlugins.clear();
diff --git a/briar-core/src/org/briarproject/plugins/PluginsModule.java b/briar-core/src/org/briarproject/plugins/PluginsModule.java
index f905d2c324fcab0a3ddcbd3fd9a7c4bbc34b0987..aca1ef52c17b1bc3129d2bf3ac5f650c81b9443a 100644
--- a/briar-core/src/org/briarproject/plugins/PluginsModule.java
+++ b/briar-core/src/org/briarproject/plugins/PluginsModule.java
@@ -1,18 +1,8 @@
 package org.briarproject.plugins;
 
-import static java.util.concurrent.TimeUnit.SECONDS;
-
-import java.util.concurrent.BlockingQueue;
-import java.util.concurrent.Executor;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.RejectedExecutionHandler;
-import java.util.concurrent.SynchronousQueue;
-import java.util.concurrent.ThreadPoolExecutor;
-
 import javax.inject.Singleton;
 
 import org.briarproject.api.lifecycle.LifecycleManager;
-import org.briarproject.api.plugins.PluginExecutor;
 import org.briarproject.api.plugins.PluginManager;
 
 import com.google.inject.AbstractModule;
@@ -20,19 +10,7 @@ import com.google.inject.Provides;
 
 public class PluginsModule extends AbstractModule {
 
-	private final ExecutorService pluginExecutor;
-
-	public PluginsModule() {
-		// The thread pool is unbounded, so use direct handoff
-		BlockingQueue<Runnable> queue = new SynchronousQueue<Runnable>();
-		// Discard tasks that are submitted during shutdown
-		RejectedExecutionHandler policy =
-				new ThreadPoolExecutor.DiscardPolicy();
-		// Create threads as required and keep them in the pool for 60 seconds
-		pluginExecutor = new ThreadPoolExecutor(0, Integer.MAX_VALUE,
-				60, SECONDS, queue, policy);
-	}
-
+	@Override
 	protected void configure() {
 		bind(Poller.class).to(PollerImpl.class);
 	}
@@ -43,10 +21,4 @@ public class PluginsModule extends AbstractModule {
 		lifecycleManager.register(pluginManager);
 		return pluginManager;
 	}
-
-	@Provides @Singleton @PluginExecutor
-	Executor getPluginExecutor(LifecycleManager lifecycleManager) {
-		lifecycleManager.registerForShutdown(pluginExecutor);
-		return pluginExecutor;
-	}
 }
diff --git a/briar-core/src/org/briarproject/plugins/PollerImpl.java b/briar-core/src/org/briarproject/plugins/PollerImpl.java
index 6d331877b4a7764ecf87e8307ff9b079c963ffa1..a92cdf4cd85c57ede04f0b9ed3dd0c998382d2fb 100644
--- a/briar-core/src/org/briarproject/plugins/PollerImpl.java
+++ b/briar-core/src/org/briarproject/plugins/PollerImpl.java
@@ -9,8 +9,8 @@ import java.util.logging.Logger;
 
 import javax.inject.Inject;
 
+import org.briarproject.api.lifecycle.IoExecutor;
 import org.briarproject.api.plugins.Plugin;
-import org.briarproject.api.plugins.PluginExecutor;
 import org.briarproject.api.system.Timer;
 import org.briarproject.api.transport.ConnectionRegistry;
 
@@ -19,14 +19,14 @@ class PollerImpl implements Poller {
 	private static final Logger LOG =
 			Logger.getLogger(PollerImpl.class.getName());
 
-	private final Executor pluginExecutor;
+	private final Executor ioExecutor;
 	private final ConnectionRegistry connRegistry;
 	private final Timer timer;
 
 	@Inject
-	PollerImpl(@PluginExecutor Executor pluginExecutor,
-			ConnectionRegistry connRegistry, Timer timer) {
-		this.pluginExecutor = pluginExecutor;
+	PollerImpl(@IoExecutor Executor ioExecutor, ConnectionRegistry connRegistry,
+			Timer timer) {
+		this.ioExecutor = ioExecutor;
 		this.connRegistry = connRegistry;
 		this.timer = timer;
 	}
@@ -49,7 +49,7 @@ class PollerImpl implements Poller {
 	}
 
 	public void pollNow(final Plugin p) {
-		pluginExecutor.execute(new Runnable() {
+		ioExecutor.execute(new Runnable() {
 			public void run() {
 				if(LOG.isLoggable(INFO))
 					LOG.info("Polling " + p.getClass().getSimpleName());
@@ -66,6 +66,7 @@ class PollerImpl implements Poller {
 			this.plugin = plugin;
 		}
 
+		@Override
 		public void run() {
 			pollNow(plugin);
 			schedule(plugin, false);
diff --git a/briar-core/src/org/briarproject/plugins/file/FilePlugin.java b/briar-core/src/org/briarproject/plugins/file/FilePlugin.java
index 3e5854524caabfa87a12d93a8ac3cf99cb1c820a..60628f33e066861fe4d23f7ac42b772fd1e6570d 100644
--- a/briar-core/src/org/briarproject/plugins/file/FilePlugin.java
+++ b/briar-core/src/org/briarproject/plugins/file/FilePlugin.java
@@ -25,7 +25,7 @@ public abstract class FilePlugin implements SimplexPlugin {
 	private static final Logger LOG =
 			Logger.getLogger(FilePlugin.class.getName());
 
-	protected final Executor pluginExecutor;
+	protected final Executor ioExecutor;
 	protected final FileUtils fileUtils;
 	protected final SimplexPluginCallback callback;
 	protected final int maxFrameLength;
@@ -38,10 +38,10 @@ public abstract class FilePlugin implements SimplexPlugin {
 	protected abstract void writerFinished(File f);
 	protected abstract void readerFinished(File f);
 
-	protected FilePlugin(Executor pluginExecutor, FileUtils fileUtils,
+	protected FilePlugin(Executor ioExecutor, FileUtils fileUtils,
 			SimplexPluginCallback callback, int maxFrameLength,
 			long maxLatency) {
-		this.pluginExecutor = pluginExecutor;
+		this.ioExecutor = ioExecutor;
 		this.fileUtils = fileUtils;
 		this.callback = callback;
 		this.maxFrameLength = maxFrameLength;
@@ -100,7 +100,7 @@ public abstract class FilePlugin implements SimplexPlugin {
 
 	protected void createReaderFromFile(final File f) {
 		if(!running) return;
-		pluginExecutor.execute(new ReaderCreator(f));
+		ioExecutor.execute(new ReaderCreator(f));
 	}
 
 	private class ReaderCreator implements Runnable {
diff --git a/briar-core/src/org/briarproject/plugins/tcp/LanTcpPlugin.java b/briar-core/src/org/briarproject/plugins/tcp/LanTcpPlugin.java
index d4d3c506246aa67e55fdc6f237d504bbe1b952e4..39e4b94184f48dce3b6c073903713b7a59f34aba 100644
--- a/briar-core/src/org/briarproject/plugins/tcp/LanTcpPlugin.java
+++ b/briar-core/src/org/briarproject/plugins/tcp/LanTcpPlugin.java
@@ -16,9 +16,9 @@ class LanTcpPlugin extends TcpPlugin {
 
 	static final TransportId ID = new TransportId("lan");
 
-	LanTcpPlugin(Executor pluginExecutor, DuplexPluginCallback callback,
+	LanTcpPlugin(Executor ioExecutor, DuplexPluginCallback callback,
 			int maxFrameLength, long maxLatency, long pollingInterval) {
-		super(pluginExecutor, callback, maxFrameLength, maxLatency,
+		super(ioExecutor, callback, maxFrameLength, maxLatency,
 				pollingInterval);
 	}
 
diff --git a/briar-core/src/org/briarproject/plugins/tcp/LanTcpPluginFactory.java b/briar-core/src/org/briarproject/plugins/tcp/LanTcpPluginFactory.java
index eb24e4550c265bf23aad97415fd5c648827c881d..02b53eab951ec8326377073e2a01bdc5d5b2559d 100644
--- a/briar-core/src/org/briarproject/plugins/tcp/LanTcpPluginFactory.java
+++ b/briar-core/src/org/briarproject/plugins/tcp/LanTcpPluginFactory.java
@@ -13,10 +13,10 @@ public class LanTcpPluginFactory implements DuplexPluginFactory {
 	private static final long MAX_LATENCY = 60 * 1000; // 1 minute
 	private static final long POLLING_INTERVAL = 60 * 1000; // 1 minute
 
-	private final Executor pluginExecutor;
+	private final Executor ioExecutor;
 
-	public LanTcpPluginFactory(Executor pluginExecutor) {
-		this.pluginExecutor = pluginExecutor;
+	public LanTcpPluginFactory(Executor ioExecutor) {
+		this.ioExecutor = ioExecutor;
 	}
 
 	public TransportId getId() {
@@ -24,7 +24,7 @@ public class LanTcpPluginFactory implements DuplexPluginFactory {
 	}
 
 	public DuplexPlugin createPlugin(DuplexPluginCallback callback) {
-		return new LanTcpPlugin(pluginExecutor, callback, MAX_FRAME_LENGTH,
+		return new LanTcpPlugin(ioExecutor, callback, MAX_FRAME_LENGTH,
 				MAX_LATENCY, POLLING_INTERVAL);
 	}
 }
diff --git a/briar-core/src/org/briarproject/plugins/tcp/TcpPlugin.java b/briar-core/src/org/briarproject/plugins/tcp/TcpPlugin.java
index c5de9d6c86d8c5727463e32e752b355141a090dc..a8d3e8dde839007fbe89ff25e6a22bed292627c4 100644
--- a/briar-core/src/org/briarproject/plugins/tcp/TcpPlugin.java
+++ b/briar-core/src/org/briarproject/plugins/tcp/TcpPlugin.java
@@ -35,7 +35,7 @@ abstract class TcpPlugin implements DuplexPlugin {
 	private static final Logger LOG =
 			Logger.getLogger(TcpPlugin.class.getName());
 
-	protected final Executor pluginExecutor;
+	protected final Executor ioExecutor;
 	protected final DuplexPluginCallback callback;
 	protected final int maxFrameLength;
 	protected final long maxLatency, pollingInterval;
@@ -52,9 +52,9 @@ abstract class TcpPlugin implements DuplexPlugin {
 	/** Returns true if connections to the given address can be attempted. */
 	protected abstract boolean isConnectable(InetSocketAddress remote);
 
-	protected TcpPlugin(Executor pluginExecutor, DuplexPluginCallback callback,
+	protected TcpPlugin(Executor ioExecutor, DuplexPluginCallback callback,
 			int maxFrameLength, long maxLatency, long pollingInterval) {
-		this.pluginExecutor = pluginExecutor;
+		this.ioExecutor = ioExecutor;
 		this.callback = callback;
 		this.maxFrameLength = maxFrameLength;
 		this.maxLatency = maxLatency;
@@ -76,7 +76,7 @@ abstract class TcpPlugin implements DuplexPlugin {
 	}
 
 	protected void bind() {
-		pluginExecutor.execute(new Runnable() {
+		ioExecutor.execute(new Runnable() {
 			public void run() {
 				if(!running) return;
 				ServerSocket ss = null;
@@ -172,7 +172,7 @@ abstract class TcpPlugin implements DuplexPlugin {
 	}
 
 	private void connectAndCallBack(final ContactId c) {
-		pluginExecutor.execute(new Runnable() {
+		ioExecutor.execute(new Runnable() {
 			public void run() {
 				DuplexTransportConnection d = createConnection(c);
 				if(d != null) callback.outgoingConnectionCreated(c, d);
diff --git a/briar-core/src/org/briarproject/plugins/tcp/WanTcpPlugin.java b/briar-core/src/org/briarproject/plugins/tcp/WanTcpPlugin.java
index 7a403b63682d1862200076eda88d8e2804c6151f..6e2e3e754cf814278a0929ad5363db17d50bb466 100644
--- a/briar-core/src/org/briarproject/plugins/tcp/WanTcpPlugin.java
+++ b/briar-core/src/org/briarproject/plugins/tcp/WanTcpPlugin.java
@@ -20,10 +20,10 @@ class WanTcpPlugin extends TcpPlugin {
 
 	private volatile MappingResult mappingResult;
 
-	WanTcpPlugin(Executor pluginExecutor, DuplexPluginCallback callback,
+	WanTcpPlugin(Executor ioExecutor, DuplexPluginCallback callback,
 			int maxFrameLength, long maxLatency, long pollingInterval,
 			PortMapper portMapper) {
-		super(pluginExecutor, callback, maxFrameLength, maxLatency,
+		super(ioExecutor, callback, maxFrameLength, maxLatency,
 				pollingInterval);
 		this.portMapper = portMapper;
 	}
diff --git a/briar-core/src/org/briarproject/plugins/tcp/WanTcpPluginFactory.java b/briar-core/src/org/briarproject/plugins/tcp/WanTcpPluginFactory.java
index a6de8017313a09fc3ade4fe2a4351669c60beacc..f478bbc295fe13b3825536309fadf3be9f590dcc 100644
--- a/briar-core/src/org/briarproject/plugins/tcp/WanTcpPluginFactory.java
+++ b/briar-core/src/org/briarproject/plugins/tcp/WanTcpPluginFactory.java
@@ -14,12 +14,12 @@ public class WanTcpPluginFactory implements DuplexPluginFactory {
 	private static final long MAX_LATENCY = 60 * 1000; // 1 minute
 	private static final long POLLING_INTERVAL = 5 * 60 * 1000; // 5 minutes
 
-	private final Executor pluginExecutor;
+	private final Executor ioExecutor;
 	private final ShutdownManager shutdownManager;
 
-	public WanTcpPluginFactory(Executor pluginExecutor,
+	public WanTcpPluginFactory(Executor ioExecutor,
 			ShutdownManager shutdownManager) {
-		this.pluginExecutor = pluginExecutor;
+		this.ioExecutor = ioExecutor;
 		this.shutdownManager = shutdownManager;
 	}
 
@@ -28,7 +28,7 @@ public class WanTcpPluginFactory implements DuplexPluginFactory {
 	}
 
 	public DuplexPlugin createPlugin(DuplexPluginCallback callback) {
-		return new WanTcpPlugin(pluginExecutor, callback, MAX_FRAME_LENGTH,
+		return new WanTcpPlugin(ioExecutor, callback, MAX_FRAME_LENGTH,
 				MAX_LATENCY, POLLING_INTERVAL,
 				new PortMapperImpl(shutdownManager));
 	}
diff --git a/briar-core/src/org/briarproject/reliability/ReliabilityLayerFactoryImpl.java b/briar-core/src/org/briarproject/reliability/ReliabilityLayerFactoryImpl.java
index 0cb3716cbf98615b897fbf5e42eee68cb2b7e8c9..7aee1db755b395e602cf7e2a296ae1d4a198cc9f 100644
--- a/briar-core/src/org/briarproject/reliability/ReliabilityLayerFactoryImpl.java
+++ b/briar-core/src/org/briarproject/reliability/ReliabilityLayerFactoryImpl.java
@@ -4,7 +4,7 @@ import java.util.concurrent.Executor;
 
 import javax.inject.Inject;
 
-import org.briarproject.api.reliability.ReliabilityExecutor;
+import org.briarproject.api.lifecycle.IoExecutor;
 import org.briarproject.api.reliability.ReliabilityLayer;
 import org.briarproject.api.reliability.ReliabilityLayerFactory;
 import org.briarproject.api.reliability.WriteHandler;
@@ -13,16 +13,16 @@ import org.briarproject.system.SystemClock;
 
 class ReliabilityLayerFactoryImpl implements ReliabilityLayerFactory {
 
-	private final Executor executor;
+	private final Executor ioExecutor;
 	private final Clock clock;
 
 	@Inject
-	ReliabilityLayerFactoryImpl(@ReliabilityExecutor Executor executor) {
-		this.executor = executor;
+	ReliabilityLayerFactoryImpl(@IoExecutor Executor ioExecutor) {
+		this.ioExecutor = ioExecutor;
 		clock = new SystemClock();
 	}
 
 	public ReliabilityLayer createReliabilityLayer(WriteHandler writeHandler) {
-		return new ReliabilityLayerImpl(executor, clock, writeHandler);
+		return new ReliabilityLayerImpl(ioExecutor, clock, writeHandler);
 	}
 }
diff --git a/briar-core/src/org/briarproject/reliability/ReliabilityModule.java b/briar-core/src/org/briarproject/reliability/ReliabilityModule.java
index 691b52cb79df013ec139e1a9f8be462540422faf..aabd18e8754d40674096aa71fc38246c96b58f52 100644
--- a/briar-core/src/org/briarproject/reliability/ReliabilityModule.java
+++ b/briar-core/src/org/briarproject/reliability/ReliabilityModule.java
@@ -1,46 +1,14 @@
 package org.briarproject.reliability;
 
-import static java.util.concurrent.TimeUnit.SECONDS;
-
-import java.util.concurrent.BlockingQueue;
-import java.util.concurrent.Executor;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.RejectedExecutionHandler;
-import java.util.concurrent.SynchronousQueue;
-import java.util.concurrent.ThreadPoolExecutor;
-
-import javax.inject.Singleton;
-
-import org.briarproject.api.lifecycle.LifecycleManager;
-import org.briarproject.api.reliability.ReliabilityExecutor;
 import org.briarproject.api.reliability.ReliabilityLayerFactory;
 
 import com.google.inject.AbstractModule;
-import com.google.inject.Provides;
 
 public class ReliabilityModule extends AbstractModule {
 
-	private final ExecutorService reliabilityExecutor;
-
-	public ReliabilityModule() {
-		// The thread pool is unbounded, so use direct handoff
-		BlockingQueue<Runnable> queue = new SynchronousQueue<Runnable>();
-		// Discard tasks that are submitted during shutdown
-		RejectedExecutionHandler policy =
-				new ThreadPoolExecutor.DiscardPolicy();
-		// Create threads as required and keep them in the pool for 60 seconds
-		reliabilityExecutor = new ThreadPoolExecutor(0, Integer.MAX_VALUE,
-				60, SECONDS, queue, policy);
-	}
-
+	@Override
 	protected void configure() {
 		bind(ReliabilityLayerFactory.class).to(
 				ReliabilityLayerFactoryImpl.class);
 	}
-
-	@Provides @Singleton @ReliabilityExecutor
-	Executor getReliabilityExecutor(LifecycleManager lifecycleManager) {
-		lifecycleManager.registerForShutdown(reliabilityExecutor);
-		return reliabilityExecutor;
-	}
 }
diff --git a/briar-core/src/org/briarproject/transport/ConnectionDispatcherImpl.java b/briar-core/src/org/briarproject/transport/ConnectionDispatcherImpl.java
index f37495e9f34e856499aef2ab82a39e8170990fa4..9844ad30e349b8b6837435a870650896e32897cd 100644
--- a/briar-core/src/org/briarproject/transport/ConnectionDispatcherImpl.java
+++ b/briar-core/src/org/briarproject/transport/ConnectionDispatcherImpl.java
@@ -14,6 +14,7 @@ import javax.inject.Inject;
 import org.briarproject.api.ContactId;
 import org.briarproject.api.TransportId;
 import org.briarproject.api.db.DbException;
+import org.briarproject.api.lifecycle.IoExecutor;
 import org.briarproject.api.messaging.duplex.DuplexConnectionFactory;
 import org.briarproject.api.messaging.simplex.SimplexConnectionFactory;
 import org.briarproject.api.plugins.duplex.DuplexTransportConnection;
@@ -22,31 +23,30 @@ import org.briarproject.api.plugins.simplex.SimplexTransportWriter;
 import org.briarproject.api.transport.ConnectionContext;
 import org.briarproject.api.transport.ConnectionDispatcher;
 import org.briarproject.api.transport.ConnectionRecogniser;
-import org.briarproject.api.transport.IncomingConnectionExecutor;
 
 class ConnectionDispatcherImpl implements ConnectionDispatcher {
 
 	private static final Logger LOG =
 			Logger.getLogger(ConnectionDispatcherImpl.class.getName());
 
-	private final Executor connExecutor;
+	private final Executor ioExecutor;
 	private final ConnectionRecogniser recogniser;
 	private final SimplexConnectionFactory simplexConnFactory;
 	private final DuplexConnectionFactory duplexConnFactory;
 
 	@Inject
-	ConnectionDispatcherImpl(@IncomingConnectionExecutor Executor connExecutor,
+	ConnectionDispatcherImpl(@IoExecutor Executor ioExecutor,
 			ConnectionRecogniser recogniser,
 			SimplexConnectionFactory simplexConnFactory,
 			DuplexConnectionFactory duplexConnFactory) {
-		this.connExecutor = connExecutor;
+		this.ioExecutor = ioExecutor;
 		this.recogniser = recogniser;
 		this.simplexConnFactory = simplexConnFactory;
 		this.duplexConnFactory = duplexConnFactory;
 	}
 
 	public void dispatchReader(TransportId t, SimplexTransportReader r) {
-		connExecutor.execute(new DispatchSimplexConnection(t, r));
+		ioExecutor.execute(new DispatchSimplexConnection(t, r));
 	}
 
 	public void dispatchWriter(ContactId c, TransportId t,
@@ -56,7 +56,7 @@ class ConnectionDispatcherImpl implements ConnectionDispatcher {
 
 	public void dispatchIncomingConnection(TransportId t,
 			DuplexTransportConnection d) {
-		connExecutor.execute(new DispatchDuplexConnection(t, d));
+		ioExecutor.execute(new DispatchDuplexConnection(t, d));
 	}
 
 	public void dispatchOutgoingConnection(ContactId c, TransportId t,
diff --git a/briar-core/src/org/briarproject/transport/TransportModule.java b/briar-core/src/org/briarproject/transport/TransportModule.java
index b53c7f7c1368825102da091dbaebd466ed06ee80..3fec3a8068125b97a06ac2885f1bf7aa68c340b5 100644
--- a/briar-core/src/org/briarproject/transport/TransportModule.java
+++ b/briar-core/src/org/briarproject/transport/TransportModule.java
@@ -1,14 +1,5 @@
 package org.briarproject.transport;
 
-import static java.util.concurrent.TimeUnit.SECONDS;
-
-import java.util.concurrent.BlockingQueue;
-import java.util.concurrent.Executor;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.RejectedExecutionHandler;
-import java.util.concurrent.SynchronousQueue;
-import java.util.concurrent.ThreadPoolExecutor;
-
 import javax.inject.Singleton;
 
 import org.briarproject.api.crypto.KeyManager;
@@ -18,26 +9,13 @@ import org.briarproject.api.transport.ConnectionReaderFactory;
 import org.briarproject.api.transport.ConnectionRecogniser;
 import org.briarproject.api.transport.ConnectionRegistry;
 import org.briarproject.api.transport.ConnectionWriterFactory;
-import org.briarproject.api.transport.IncomingConnectionExecutor;
 
 import com.google.inject.AbstractModule;
 import com.google.inject.Provides;
 
 public class TransportModule extends AbstractModule {
 
-	private final ExecutorService incomingConnectionExecutor;
-
-	public TransportModule() {
-		// The thread pool is unbounded, so use direct handoff
-		BlockingQueue<Runnable> queue = new SynchronousQueue<Runnable>();
-		// Discard tasks that are submitted during shutdown
-		RejectedExecutionHandler policy =
-				new ThreadPoolExecutor.DiscardPolicy();
-		// Create threads as required and keep them in the pool for 60 seconds
-		incomingConnectionExecutor = new ThreadPoolExecutor(0,
-				Integer.MAX_VALUE, 60, SECONDS, queue, policy);
-	}
-
+	@Override
 	protected void configure() {
 		bind(ConnectionDispatcher.class).to(ConnectionDispatcherImpl.class);
 		bind(ConnectionReaderFactory.class).to(
@@ -55,10 +33,4 @@ public class TransportModule extends AbstractModule {
 		lifecycleManager.register(keyManager);
 		return keyManager;
 	}
-
-	@Provides @Singleton @IncomingConnectionExecutor
-	Executor getIncomingConnectionExecutor(LifecycleManager lifecycleManager) {
-		lifecycleManager.registerForShutdown(incomingConnectionExecutor);
-		return incomingConnectionExecutor;
-	}
 }
diff --git a/briar-desktop/src/org/briarproject/lifecycle/DesktopLifecycleModule.java b/briar-desktop/src/org/briarproject/lifecycle/DesktopLifecycleModule.java
index 8395bc8cf5cbcef2c541378b720e3fed6446af61..ea12c7098756ccb548faac3f66843db4f6573ddc 100644
--- a/briar-desktop/src/org/briarproject/lifecycle/DesktopLifecycleModule.java
+++ b/briar-desktop/src/org/briarproject/lifecycle/DesktopLifecycleModule.java
@@ -4,21 +4,20 @@ import org.briarproject.api.lifecycle.LifecycleManager;
 import org.briarproject.api.lifecycle.ShutdownManager;
 import org.briarproject.util.OsUtils;
 
-import com.google.inject.AbstractModule;
 import com.google.inject.Singleton;
 
-public class DesktopLifecycleModule extends AbstractModule {
+public class DesktopLifecycleModule extends LifecycleModule {
 
+	@Override
 	protected void configure() {
 		bind(LifecycleManager.class).to(
 				LifecycleManagerImpl.class).in(Singleton.class);
 		if(OsUtils.isWindows()) {
 			bind(ShutdownManager.class).to(
-				WindowsShutdownManagerImpl.class).in(
-					Singleton.class);
+					WindowsShutdownManagerImpl.class).in(Singleton.class);
 		} else {
 			bind(ShutdownManager.class).to(
-				ShutdownManagerImpl.class).in(Singleton.class);
+					ShutdownManagerImpl.class).in(Singleton.class);
 		}
 	}
 }
diff --git a/briar-desktop/src/org/briarproject/plugins/DesktopPluginsModule.java b/briar-desktop/src/org/briarproject/plugins/DesktopPluginsModule.java
index 021b80301e2ce692a5cea8a32e9e1badce878053..58be0ce511e66d642d7379adb8c0bc8d2901b5fb 100644
--- a/briar-desktop/src/org/briarproject/plugins/DesktopPluginsModule.java
+++ b/briar-desktop/src/org/briarproject/plugins/DesktopPluginsModule.java
@@ -5,8 +5,8 @@ import java.util.Collection;
 import java.util.concurrent.Executor;
 
 import org.briarproject.api.crypto.CryptoComponent;
+import org.briarproject.api.lifecycle.IoExecutor;
 import org.briarproject.api.lifecycle.ShutdownManager;
-import org.briarproject.api.plugins.PluginExecutor;
 import org.briarproject.api.plugins.duplex.DuplexPluginConfig;
 import org.briarproject.api.plugins.duplex.DuplexPluginFactory;
 import org.briarproject.api.plugins.simplex.SimplexPluginConfig;
@@ -19,18 +19,15 @@ import org.briarproject.plugins.modem.ModemPluginFactory;
 import org.briarproject.plugins.tcp.LanTcpPluginFactory;
 import org.briarproject.plugins.tcp.WanTcpPluginFactory;
 
-import com.google.inject.AbstractModule;
 import com.google.inject.Provides;
 
-public class DesktopPluginsModule extends AbstractModule {
-
-	public void configure() {}
+public class DesktopPluginsModule extends PluginsModule {
 
 	@Provides
-	SimplexPluginConfig getSimplexPluginConfig(
-			@PluginExecutor Executor pluginExecutor, FileUtils fileUtils) {
+	SimplexPluginConfig getSimplexPluginConfig(@IoExecutor Executor ioExecutor,
+			FileUtils fileUtils) {
 		SimplexPluginFactory removable =
-				new RemovableDrivePluginFactory(pluginExecutor, fileUtils);
+				new RemovableDrivePluginFactory(ioExecutor, fileUtils);
 		final Collection<SimplexPluginFactory> factories =
 				Arrays.asList(removable);
 		return new SimplexPluginConfig() {
@@ -41,16 +38,15 @@ public class DesktopPluginsModule extends AbstractModule {
 	}
 
 	@Provides
-	DuplexPluginConfig getDuplexPluginConfig(
-			@PluginExecutor Executor pluginExecutor,
+	DuplexPluginConfig getDuplexPluginConfig(@IoExecutor Executor ioExecutor,
 			CryptoComponent crypto, ReliabilityLayerFactory reliabilityFactory,
 			ShutdownManager shutdownManager) {
 		DuplexPluginFactory bluetooth = new BluetoothPluginFactory(
-				pluginExecutor, crypto.getSecureRandom());
-		DuplexPluginFactory modem = new ModemPluginFactory(pluginExecutor,
+				ioExecutor, crypto.getSecureRandom());
+		DuplexPluginFactory modem = new ModemPluginFactory(ioExecutor,
 				reliabilityFactory);
-		DuplexPluginFactory lan = new LanTcpPluginFactory(pluginExecutor);
-		DuplexPluginFactory wan = new WanTcpPluginFactory(pluginExecutor,
+		DuplexPluginFactory lan = new LanTcpPluginFactory(ioExecutor);
+		DuplexPluginFactory wan = new WanTcpPluginFactory(ioExecutor,
 				shutdownManager);
 		final Collection<DuplexPluginFactory> factories =
 				Arrays.asList(bluetooth, modem, lan, wan);
diff --git a/briar-desktop/src/org/briarproject/plugins/bluetooth/BluetoothPlugin.java b/briar-desktop/src/org/briarproject/plugins/bluetooth/BluetoothPlugin.java
index 364b44a51a9db2dd46c0408391f3e41b053f29b6..82f69bee237d01ba3ccd0de5a4537507eda9b198 100644
--- a/briar-desktop/src/org/briarproject/plugins/bluetooth/BluetoothPlugin.java
+++ b/briar-desktop/src/org/briarproject/plugins/bluetooth/BluetoothPlugin.java
@@ -42,7 +42,7 @@ class BluetoothPlugin implements DuplexPlugin {
 			Logger.getLogger(BluetoothPlugin.class.getName());
 	private static final int UUID_BYTES = 16;
 
-	private final Executor pluginExecutor;
+	private final Executor ioExecutor;
 	private final Clock clock;
 	private final SecureRandom secureRandom;
 	private final DuplexPluginCallback callback;
@@ -54,10 +54,10 @@ class BluetoothPlugin implements DuplexPlugin {
 	private volatile StreamConnectionNotifier socket = null;
 	private volatile LocalDevice localDevice = null;
 
-	BluetoothPlugin(Executor pluginExecutor, Clock clock,
-			SecureRandom secureRandom, DuplexPluginCallback callback,
-			int maxFrameLength, long maxLatency, long pollingInterval) {
-		this.pluginExecutor = pluginExecutor;
+	BluetoothPlugin(Executor ioExecutor, Clock clock, SecureRandom secureRandom,
+			DuplexPluginCallback callback, int maxFrameLength, long maxLatency,
+			long pollingInterval) {
+		this.ioExecutor = ioExecutor;
 		this.clock = clock;
 		this.secureRandom = secureRandom;
 		this.callback = callback;
@@ -96,7 +96,7 @@ class BluetoothPlugin implements DuplexPlugin {
 	}
 
 	private void bind() {
-		pluginExecutor.execute(new Runnable() {
+		ioExecutor.execute(new Runnable() {
 			public void run() {
 				if(!running) return;
 				// Advertise the Bluetooth address to contacts
@@ -197,7 +197,7 @@ class BluetoothPlugin implements DuplexPlugin {
 			if(StringUtils.isNullOrEmpty(address)) continue;
 			final String uuid = e.getValue().get("uuid");
 			if(StringUtils.isNullOrEmpty(uuid)) continue;
-			pluginExecutor.execute(new Runnable() {
+			ioExecutor.execute(new Runnable() {
 				public void run() {
 					if(!running) return;
 					StreamConnection s = connect(makeUrl(address, uuid));
diff --git a/briar-desktop/src/org/briarproject/plugins/bluetooth/BluetoothPluginFactory.java b/briar-desktop/src/org/briarproject/plugins/bluetooth/BluetoothPluginFactory.java
index 8e4963f7909e2d038fddc28681c6ea948c03e050..446846305b840605d64c9504fc2a2a3077689920 100644
--- a/briar-desktop/src/org/briarproject/plugins/bluetooth/BluetoothPluginFactory.java
+++ b/briar-desktop/src/org/briarproject/plugins/bluetooth/BluetoothPluginFactory.java
@@ -16,13 +16,13 @@ public class BluetoothPluginFactory implements DuplexPluginFactory {
 	private static final long MAX_LATENCY = 60 * 1000; // 1 minute
 	private static final long POLLING_INTERVAL = 3 * 60 * 1000; // 3 minutes
 
-	private final Executor pluginExecutor;
+	private final Executor ioExecutor;
 	private final SecureRandom secureRandom;
 	private final Clock clock;
 
-	public BluetoothPluginFactory(Executor pluginExecutor,
+	public BluetoothPluginFactory(Executor ioExecutor,
 			SecureRandom secureRandom) {
-		this.pluginExecutor = pluginExecutor;
+		this.ioExecutor = ioExecutor;
 		this.secureRandom = secureRandom;
 		clock = new SystemClock();
 	}
@@ -32,7 +32,7 @@ public class BluetoothPluginFactory implements DuplexPluginFactory {
 	}
 
 	public DuplexPlugin createPlugin(DuplexPluginCallback callback) {
-		return new BluetoothPlugin(pluginExecutor, clock, secureRandom,
-				callback, MAX_FRAME_LENGTH, MAX_LATENCY, POLLING_INTERVAL);
+		return new BluetoothPlugin(ioExecutor, clock, secureRandom, callback,
+				MAX_FRAME_LENGTH, MAX_LATENCY, POLLING_INTERVAL);
 	}
 }
diff --git a/briar-desktop/src/org/briarproject/plugins/file/PollingRemovableDriveMonitor.java b/briar-desktop/src/org/briarproject/plugins/file/PollingRemovableDriveMonitor.java
index a7b0208c747b54746e6e7253a25df196fe4c197e..2d8fd287616093ff0ddd54b5c5f9adbdd75ad412 100644
--- a/briar-desktop/src/org/briarproject/plugins/file/PollingRemovableDriveMonitor.java
+++ b/briar-desktop/src/org/briarproject/plugins/file/PollingRemovableDriveMonitor.java
@@ -11,7 +11,7 @@ class PollingRemovableDriveMonitor implements RemovableDriveMonitor, Runnable {
 	private static final Logger LOG =
 			Logger.getLogger(PollingRemovableDriveMonitor.class.getName());
 
-	private final Executor pluginExecutor;
+	private final Executor ioExecutor;
 	private final RemovableDriveFinder finder;
 	private final long pollingInterval;
 	private final Object pollingLock = new Object();
@@ -19,9 +19,9 @@ class PollingRemovableDriveMonitor implements RemovableDriveMonitor, Runnable {
 	private volatile boolean running = false;
 	private volatile Callback callback = null;
 
-	public PollingRemovableDriveMonitor(Executor pluginExecutor,
+	public PollingRemovableDriveMonitor(Executor ioExecutor,
 			RemovableDriveFinder finder, long pollingInterval) {
-		this.pluginExecutor = pluginExecutor;
+		this.ioExecutor = ioExecutor;
 		this.finder = finder;
 		this.pollingInterval = pollingInterval;
 	}
@@ -29,7 +29,7 @@ class PollingRemovableDriveMonitor implements RemovableDriveMonitor, Runnable {
 	public void start(Callback callback) throws IOException {
 		this.callback = callback;
 		running = true;
-		pluginExecutor.execute(this);
+		ioExecutor.execute(this);
 	}
 
 	public void stop() throws IOException {
diff --git a/briar-desktop/src/org/briarproject/plugins/file/RemovableDrivePlugin.java b/briar-desktop/src/org/briarproject/plugins/file/RemovableDrivePlugin.java
index 76a5a0f67753c3415c2e0bf84f692af3e512049f..09ffd6f663592668cc148c7ae74af1b1cc065005 100644
--- a/briar-desktop/src/org/briarproject/plugins/file/RemovableDrivePlugin.java
+++ b/briar-desktop/src/org/briarproject/plugins/file/RemovableDrivePlugin.java
@@ -27,11 +27,11 @@ implements RemovableDriveMonitor.Callback {
 	private final RemovableDriveFinder finder;
 	private final RemovableDriveMonitor monitor;
 
-	RemovableDrivePlugin(Executor pluginExecutor, FileUtils fileUtils,
+	RemovableDrivePlugin(Executor ioExecutor, FileUtils fileUtils,
 			SimplexPluginCallback callback, RemovableDriveFinder finder,
 			RemovableDriveMonitor monitor, int maxFrameLength,
 			long maxLatency) {
-		super(pluginExecutor, fileUtils, callback, maxFrameLength, maxLatency);
+		super(ioExecutor, fileUtils, callback, maxFrameLength, maxLatency);
 		this.finder = finder;
 		this.monitor = monitor;
 	}
diff --git a/briar-desktop/src/org/briarproject/plugins/file/RemovableDrivePluginFactory.java b/briar-desktop/src/org/briarproject/plugins/file/RemovableDrivePluginFactory.java
index 5b948cde334c156102a9ec643a10717bbdfd9f39..01688d40344aee7171076077cdd4ca3b09ecfda5 100644
--- a/briar-desktop/src/org/briarproject/plugins/file/RemovableDrivePluginFactory.java
+++ b/briar-desktop/src/org/briarproject/plugins/file/RemovableDrivePluginFactory.java
@@ -17,12 +17,12 @@ public class RemovableDrivePluginFactory implements SimplexPluginFactory {
 	private static final long MAX_LATENCY = 14 * 24 * 60 * 60 * 1000;
 	private static final long POLLING_INTERVAL = 10 * 1000; // 10 seconds
 
-	private final Executor pluginExecutor;
+	private final Executor ioExecutor;
 	private final FileUtils fileUtils;
 
-	public RemovableDrivePluginFactory(Executor pluginExecutor,
+	public RemovableDrivePluginFactory(Executor ioExecutor,
 			FileUtils fileUtils) {
-		this.pluginExecutor = pluginExecutor;
+		this.ioExecutor = ioExecutor;
 		this.fileUtils = fileUtils;
 	}
 
@@ -42,16 +42,16 @@ public class RemovableDrivePluginFactory implements SimplexPluginFactory {
 		} else if(OsUtils.isMac()) {
 			// JNotify requires OS X 10.5 or newer, so we have to poll
 			finder = new MacRemovableDriveFinder();
-			monitor = new PollingRemovableDriveMonitor(pluginExecutor, finder,
+			monitor = new PollingRemovableDriveMonitor(ioExecutor, finder,
 					POLLING_INTERVAL);
 		} else if(OsUtils.isWindows()) {
 			finder = new WindowsRemovableDriveFinder();
-			monitor = new PollingRemovableDriveMonitor(pluginExecutor, finder,
+			monitor = new PollingRemovableDriveMonitor(ioExecutor, finder,
 					POLLING_INTERVAL);
 		} else {
 			return null;
 		}
-		return new RemovableDrivePlugin(pluginExecutor, fileUtils, callback,
+		return new RemovableDrivePlugin(ioExecutor, fileUtils, callback,
 				finder, monitor, MAX_FRAME_LENGTH, MAX_LATENCY);
 	}
 }
diff --git a/briar-desktop/src/org/briarproject/plugins/modem/ModemPlugin.java b/briar-desktop/src/org/briarproject/plugins/modem/ModemPlugin.java
index 7d23028cd2b4c2183814906c1a6c19c741713db4..22d45eb0254f99298522c1c702c17f47bf49f446 100644
--- a/briar-desktop/src/org/briarproject/plugins/modem/ModemPlugin.java
+++ b/briar-desktop/src/org/briarproject/plugins/modem/ModemPlugin.java
@@ -32,7 +32,7 @@ class ModemPlugin implements DuplexPlugin, Modem.Callback {
 	private static final Logger LOG =
 			Logger.getLogger(ModemPlugin.class.getName());
 
-	private final Executor pluginExecutor;
+	private final Executor ioExecutor;
 	private final ModemFactory modemFactory;
 	private final SerialPortList serialPortList;
 	private final DuplexPluginCallback callback;
@@ -43,11 +43,11 @@ class ModemPlugin implements DuplexPlugin, Modem.Callback {
 	private volatile boolean running = false;
 	private volatile Modem modem = null;
 
-	ModemPlugin(Executor pluginExecutor, ModemFactory modemFactory,
+	ModemPlugin(Executor ioExecutor, ModemFactory modemFactory,
 			SerialPortList serialPortList, DuplexPluginCallback callback,
 			int maxFrameLength, long maxLatency, long pollingInterval,
 			boolean shuffle) {
-		this.pluginExecutor = pluginExecutor;
+		this.ioExecutor = ioExecutor;
 		this.modemFactory = modemFactory;
 		this.serialPortList = serialPortList;
 		this.callback = callback;
@@ -112,7 +112,7 @@ class ModemPlugin implements DuplexPlugin, Modem.Callback {
 
 	public void poll(Collection<ContactId> connected) {
 		if(!connected.isEmpty()) return; // One at a time please
-		pluginExecutor.execute(new Runnable() {
+		ioExecutor.execute(new Runnable() {
 			public void run() {
 				poll();
 			}
diff --git a/briar-desktop/src/org/briarproject/plugins/modem/ModemPluginFactory.java b/briar-desktop/src/org/briarproject/plugins/modem/ModemPluginFactory.java
index b32dcd01ec71ff29b58682ac7c2b0e4151c74579..4fff4bbfaa066db8b1cbf83a49cef1386fe95703 100644
--- a/briar-desktop/src/org/briarproject/plugins/modem/ModemPluginFactory.java
+++ b/briar-desktop/src/org/briarproject/plugins/modem/ModemPluginFactory.java
@@ -15,14 +15,14 @@ public class ModemPluginFactory implements DuplexPluginFactory {
 	private static final long MAX_LATENCY = 60 * 1000; // 1 minute
 	private static final long POLLING_INTERVAL = 60 * 60 * 1000; // 1 hour
 
-	private final Executor pluginExecutor;
+	private final Executor ioExecutor;
 	private final ModemFactory modemFactory;
 	private final SerialPortList serialPortList;
 
-	public ModemPluginFactory(Executor pluginExecutor,
+	public ModemPluginFactory(Executor ioExecutor,
 			ReliabilityLayerFactory reliabilityFactory) {
-		this.pluginExecutor = pluginExecutor;
-		modemFactory = new ModemFactoryImpl(pluginExecutor, reliabilityFactory);
+		this.ioExecutor = ioExecutor;
+		modemFactory = new ModemFactoryImpl(ioExecutor, reliabilityFactory);
 		serialPortList = new SerialPortListImpl();
 	}
 
@@ -34,7 +34,7 @@ public class ModemPluginFactory implements DuplexPluginFactory {
 		// This plugin is not enabled by default
 		String enabled = callback.getConfig().get("enabled");
 		if(StringUtils.isNullOrEmpty(enabled)) return null;
-		return new ModemPlugin(pluginExecutor, modemFactory, serialPortList,
+		return new ModemPlugin(ioExecutor, modemFactory, serialPortList,
 				callback, MAX_FRAME_LENGTH, MAX_LATENCY, POLLING_INTERVAL,
 				true);
 	}
diff --git a/briar-tests/src/org/briarproject/TestLifecycleModule.java b/briar-tests/src/org/briarproject/TestLifecycleModule.java
index becccd4523a35d4834682595750ccf81b05a698b..580b445e2b67bc25962b917a92071fc48be37f23 100644
--- a/briar-tests/src/org/briarproject/TestLifecycleModule.java
+++ b/briar-tests/src/org/briarproject/TestLifecycleModule.java
@@ -1,7 +1,10 @@
 package org.briarproject;
 
+import java.util.concurrent.Executor;
 import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
 
+import org.briarproject.api.lifecycle.IoExecutor;
 import org.briarproject.api.lifecycle.LifecycleManager;
 import org.briarproject.api.lifecycle.Service;
 import org.briarproject.api.lifecycle.ShutdownManager;
@@ -10,6 +13,7 @@ import com.google.inject.AbstractModule;
 
 public class TestLifecycleModule extends AbstractModule {
 
+	@Override
 	protected void configure() {
 		bind(LifecycleManager.class).toInstance(new LifecycleManager() {
 
@@ -37,5 +41,7 @@ public class TestLifecycleModule extends AbstractModule {
 				return true;
 			}
 		});
+		bind(Executor.class).annotatedWith(IoExecutor.class).toInstance(
+				Executors.newCachedThreadPool());
 	}
 }
diff --git a/briar-tests/src/org/briarproject/messaging/simplex/SimplexMessagingIntegrationTest.java b/briar-tests/src/org/briarproject/messaging/simplex/SimplexMessagingIntegrationTest.java
index a32327b98d97765204bbe0ead560f3602274d408..0856bbb38bfe4679bad7bc39eee170768543299d 100644
--- a/briar-tests/src/org/briarproject/messaging/simplex/SimplexMessagingIntegrationTest.java
+++ b/briar-tests/src/org/briarproject/messaging/simplex/SimplexMessagingIntegrationTest.java
@@ -25,7 +25,7 @@ import org.briarproject.api.event.Event;
 import org.briarproject.api.event.EventListener;
 import org.briarproject.api.event.MessageAddedEvent;
 import org.briarproject.api.messaging.Group;
-import org.briarproject.api.messaging.GroupId;
+import org.briarproject.api.messaging.GroupFactory;
 import org.briarproject.api.messaging.Message;
 import org.briarproject.api.messaging.MessageFactory;
 import org.briarproject.api.messaging.MessageVerifier;
@@ -59,7 +59,6 @@ public class SimplexMessagingIntegrationTest extends BriarTestCase {
 	private final File testDir = TestUtils.getTestDirectory();
 	private final File aliceDir = new File(testDir, "alice");
 	private final File bobDir = new File(testDir, "bob");
-	private final Group group;
 	private final TransportId transportId;
 	private final byte[] initialSecret;
 	private final long epoch;
@@ -67,8 +66,6 @@ public class SimplexMessagingIntegrationTest extends BriarTestCase {
 	private Injector alice, bob;
 
 	public SimplexMessagingIntegrationTest() throws Exception {
-		GroupId groupId = new GroupId(TestUtils.getRandomId());
-		group = new Group(groupId, "Group", new byte[GROUP_SALT_LENGTH]);
 		transportId = new TransportId("id");
 		// Create matching secrets for Alice and Bob
 		initialSecret = new byte[32];
@@ -77,6 +74,7 @@ public class SimplexMessagingIntegrationTest extends BriarTestCase {
 		epoch = System.currentTimeMillis() - 2 * rotationPeriod;
 	}
 
+	@Override
 	@Before
 	public void setUp() {
 		testDir.mkdirs();
@@ -88,7 +86,7 @@ public class SimplexMessagingIntegrationTest extends BriarTestCase {
 		return Guice.createInjector(new TestDatabaseModule(dir),
 				new TestLifecycleModule(), new TestSystemModule(),
 				new CryptoModule(), new DatabaseModule(), new MessagingModule(),
-				new DuplexMessagingModule(), new SimplexMessagingModule(), 
+				new DuplexMessagingModule(), new SimplexMessagingModule(),
 				new SerialModule(), new TransportModule());
 	}
 
@@ -122,6 +120,8 @@ public class SimplexMessagingIntegrationTest extends BriarTestCase {
 				new byte[MAX_PUBLIC_KEY_LENGTH]);
 		ContactId contactId = db.addContact(bobAuthor, aliceId);
 		// Add the inbox group
+		GroupFactory gf = alice.getInstance(GroupFactory.class);
+		Group group = gf.createGroup("Group", new byte[GROUP_SALT_LENGTH]);
 		db.addGroup(group);
 		db.setInboxGroup(contactId, group);
 		// Add the transport and the endpoint
@@ -181,6 +181,8 @@ public class SimplexMessagingIntegrationTest extends BriarTestCase {
 				new byte[MAX_PUBLIC_KEY_LENGTH]);
 		ContactId contactId = db.addContact(aliceAuthor, bobId);
 		// Add the inbox group
+		GroupFactory gf = bob.getInstance(GroupFactory.class);
+		Group group = gf.createGroup("Group", new byte[GROUP_SALT_LENGTH]);
 		db.addGroup(group);
 		db.setInboxGroup(contactId, group);
 		// Add the transport and the endpoint
@@ -228,6 +230,7 @@ public class SimplexMessagingIntegrationTest extends BriarTestCase {
 		db.close();
 	}
 
+	@Override
 	@After
 	public void tearDown() {
 		TestUtils.deleteTestDirectory(testDir);
@@ -235,7 +238,7 @@ public class SimplexMessagingIntegrationTest extends BriarTestCase {
 
 	private static class MessageListener implements EventListener {
 
-		private boolean messageAdded = false;
+		private volatile boolean messageAdded = false;
 
 		public void eventOccurred(Event e) {
 			if(e instanceof MessageAddedEvent) messageAdded = true;
diff --git a/briar-tests/src/org/briarproject/plugins/PluginManagerImplTest.java b/briar-tests/src/org/briarproject/plugins/PluginManagerImplTest.java
index 7c861bc72472358c616a5d10724fb291515cf23d..43fcc4e9fdef26af36c74979487f5b9a1f8f0dc3 100644
--- a/briar-tests/src/org/briarproject/plugins/PluginManagerImplTest.java
+++ b/briar-tests/src/org/briarproject/plugins/PluginManagerImplTest.java
@@ -29,7 +29,7 @@ public class PluginManagerImplTest extends BriarTestCase {
 	public void testStartAndStop() throws Exception {
 		Clock clock = new SystemClock();
 		Mockery context = new Mockery();
-		final Executor pluginExecutor = Executors.newCachedThreadPool();
+		final Executor ioExecutor = Executors.newCachedThreadPool();
 		final SimplexPluginConfig simplexPluginConfig =
 				context.mock(SimplexPluginConfig.class);
 		final DuplexPluginConfig duplexPluginConfig =
@@ -116,7 +116,7 @@ public class PluginManagerImplTest extends BriarTestCase {
 			oneOf(simplexPlugin).stop();
 			oneOf(duplexPlugin).stop();
 		}});
-		PluginManagerImpl p = new PluginManagerImpl(pluginExecutor,
+		PluginManagerImpl p = new PluginManagerImpl(ioExecutor,
 				simplexPluginConfig, duplexPluginConfig, clock, db, poller,
 				dispatcher, uiCallback);
 		// Two plugins should be started and stopped
diff --git a/briar-tests/src/org/briarproject/plugins/modem/ModemPluginTest.java b/briar-tests/src/org/briarproject/plugins/modem/ModemPluginTest.java
index 40dc56579a50b02b80a588aba0c00f8a7b6fd96e..d008330a28e3e77cd04c19099b521fea5975c35c 100644
--- a/briar-tests/src/org/briarproject/plugins/modem/ModemPluginTest.java
+++ b/briar-tests/src/org/briarproject/plugins/modem/ModemPluginTest.java
@@ -15,7 +15,6 @@ import org.briarproject.api.ContactId;
 import org.briarproject.api.TransportProperties;
 import org.briarproject.api.plugins.duplex.DuplexPluginCallback;
 import org.briarproject.api.plugins.duplex.DuplexTransportConnection;
-
 import org.hamcrest.Description;
 import org.jmock.Expectations;
 import org.jmock.Mockery;
@@ -194,8 +193,7 @@ public class ModemPluginTest extends BriarTestCase {
 
 	@Test
 	public void testPolling() throws Exception {
-		final ExecutorService pluginExecutor =
-				Executors.newSingleThreadExecutor();
+		final ExecutorService ioExecutor = Executors.newSingleThreadExecutor();
 		Mockery context = new Mockery();
 		final ModemFactory modemFactory = context.mock(ModemFactory.class);
 		final SerialPortList serialPortList =
@@ -203,7 +201,7 @@ public class ModemPluginTest extends BriarTestCase {
 		final DuplexPluginCallback callback =
 				context.mock(DuplexPluginCallback.class);
 		// Disable shuffling for this test, it confuses jMock
-		final ModemPlugin plugin = new ModemPlugin(pluginExecutor, modemFactory,
+		final ModemPlugin plugin = new ModemPlugin(ioExecutor, modemFactory,
 				serialPortList, callback, 0, 0, 0, false);
 		final Modem modem = context.mock(Modem.class);
 		final TransportProperties local = new TransportProperties();
@@ -265,7 +263,7 @@ public class ModemPluginTest extends BriarTestCase {
 		assertTrue(plugin.start());
 		plugin.poll(Collections.<ContactId>emptyList());
 		assertTrue(disposeAction.invoked.await(5, SECONDS));
-		pluginExecutor.shutdown();
+		ioExecutor.shutdown();
 		context.assertIsSatisfied();
 	}