diff --git a/components/net/sf/briar/plugins/bluetooth/BluetoothPlugin.java b/components/net/sf/briar/plugins/bluetooth/BluetoothPlugin.java
index 253040533b6eeaca28e4f26a41de119dcd977260..bddcfa8e91a3f8b4f395c6b858f3bd68f567baee 100644
--- a/components/net/sf/briar/plugins/bluetooth/BluetoothPlugin.java
+++ b/components/net/sf/briar/plugins/bluetooth/BluetoothPlugin.java
@@ -1,14 +1,13 @@
 package net.sf.briar.plugins.bluetooth;
 
 import java.io.IOException;
+import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
-import java.util.HashSet;
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.Random;
-import java.util.Set;
 import java.util.concurrent.Executor;
 import java.util.concurrent.Executors;
 import java.util.concurrent.ScheduledExecutorService;
@@ -48,8 +47,9 @@ class BluetoothPlugin implements StreamPlugin {
 	private final StreamPluginCallback callback;
 	private final long pollingInterval;
 	private final Object discoveryLock = new Object();
+	private final Object localPropertiesLock = new Object();
 	private final ScheduledExecutorService scheduler;
-	private final Set<StreamConnectionNotifier> sockets; // Locking: this
+	private final Collection<StreamConnectionNotifier> sockets; // Locking: this
 
 	private boolean running = false; // Locking: this
 	private LocalDevice localDevice = null; // Locking: this
@@ -61,7 +61,7 @@ class BluetoothPlugin implements StreamPlugin {
 		this.callback = callback;
 		this.pollingInterval = pollingInterval;
 		scheduler = Executors.newScheduledThreadPool(0);
-		sockets = new HashSet<StreamConnectionNotifier>();
+		sockets = new ArrayList<StreamConnectionNotifier>();
 	}
 
 	public TransportId getId() {
@@ -70,19 +70,21 @@ class BluetoothPlugin implements StreamPlugin {
 
 	public void start() throws IOException {
 		// Initialise the Bluetooth stack
+		LocalDevice localDevice;
 		try {
-			synchronized(this) {
-				running = true;
-				localDevice = LocalDevice.getLocalDevice();
-				if(LOG.isLoggable(Level.INFO))
-					LOG.info("Address " + localDevice.getBluetoothAddress());
-			} 
+			localDevice = LocalDevice.getLocalDevice();
 		} catch(UnsatisfiedLinkError e) {
 			// On Linux the user may need to install libbluetooth-dev
 			if(OsUtils.isLinux())
 				callback.showMessage("BLUETOOTH_INSTALL_LIBS");
 			throw new IOException(e.toString());
 		}
+		if(LOG.isLoggable(Level.INFO))
+			LOG.info("Address " + localDevice.getBluetoothAddress());
+		synchronized(this) {
+			running = true;
+			this.localDevice = localDevice;
+		} 
 		pluginExecutor.execute(new Runnable() {
 			public void run() {
 				bind();
@@ -91,13 +93,11 @@ class BluetoothPlugin implements StreamPlugin {
 	}
 
 	private void bind() {
-		String uuid;
 		synchronized(this) {
 			if(!running) return;
-			uuid = getUuid();
-			makeDeviceDiscoverable();
 		}
-		String url = "btspp://localhost:" + uuid + ";name=RFCOMM";
+		makeDeviceDiscoverable();
+		String url = "btspp://localhost:" + getUuid() + ";name=RFCOMM";
 		StreamConnectionNotifier scn;
 		try {
 			scn = (StreamConnectionNotifier) Connector.open(url);
@@ -115,24 +115,30 @@ class BluetoothPlugin implements StreamPlugin {
 		acceptContactConnections(scn);
 	}
 
-	private synchronized String getUuid() {
-		assert running;
-		TransportProperties p = callback.getLocalProperties();
-		String uuid = p.get("uuid");
-		if(uuid == null) {
-			// Generate a (weakly) random UUID and store it
-			byte[] b = new byte[16];
-			new Random().nextBytes(b);
-			uuid = StringUtils.toHexString(b);
-			p.put("uuid", uuid);
-			callback.setLocalProperties(p);
+	private String getUuid() {
+		// FIXME: Avoid making alien calls with a lock held
+		synchronized(localPropertiesLock) {
+			TransportProperties p = callback.getLocalProperties();
+			String uuid = p.get("uuid");
+			if(uuid == null) {
+				// Generate a (weakly) random UUID and store it
+				byte[] b = new byte[16];
+				new Random().nextBytes(b);
+				uuid = StringUtils.toHexString(b);
+				p.put("uuid", uuid);
+				callback.setLocalProperties(p);
+			}
+			return uuid;
 		}
-		return uuid;
 	}
 
-	private synchronized void makeDeviceDiscoverable() {
-		assert running;
+	private void makeDeviceDiscoverable() {
 		// Try to make the device discoverable (requires root on Linux)
+		LocalDevice localDevice;
+		synchronized(this) {
+			if(!running) return;
+			localDevice = this.localDevice;
+		}
 		try {
 			localDevice.setDiscoverable(DiscoveryAgent.GIAC);
 		} catch(BluetoothStateException e) {
@@ -140,9 +146,12 @@ class BluetoothPlugin implements StreamPlugin {
 		}
 		// Advertise the address to contacts if the device is discoverable
 		if(localDevice.getDiscoverable() != DiscoveryAgent.NOT_DISCOVERABLE) {
-			TransportProperties p = callback.getLocalProperties();
-			p.put("address", localDevice.getBluetoothAddress());
-			callback.setLocalProperties(p);
+			// FIXME: Avoid making alien calls with a lock held
+			synchronized(localPropertiesLock) {
+				TransportProperties p = callback.getLocalProperties();
+				p.put("address", localDevice.getBluetoothAddress());
+				callback.setLocalProperties(p);
+			}
 		}
 	}
 
@@ -174,16 +183,18 @@ class BluetoothPlugin implements StreamPlugin {
 		}
 	}
 
-	public synchronized void stop() {
-		running = false;
-		localDevice = null;
-		scheduler.shutdownNow();
-		for(StreamConnectionNotifier scn : sockets) tryToClose(scn);
-		sockets.clear();
-		if(socket != null) {
-			tryToClose(socket);
-			socket = null;
+	public void stop() {
+		synchronized(this) {
+			running = false;
+			localDevice = null;
+			for(StreamConnectionNotifier scn : sockets) tryToClose(scn);
+			sockets.clear();
+			if(socket != null) {
+				tryToClose(socket);
+				socket = null;
+			}
 		}
+		scheduler.shutdownNow();
 	}
 
 	public boolean shouldPoll() {
@@ -224,11 +235,12 @@ class BluetoothPlugin implements StreamPlugin {
 
 	private Map<ContactId, String> discoverContactUrls(
 			Map<ContactId, TransportProperties> remote) {
-		DiscoveryAgent discoveryAgent;
+		LocalDevice localDevice;
 		synchronized(this) {
 			if(!running) return Collections.emptyMap();
-			discoveryAgent = localDevice.getDiscoveryAgent();
+			localDevice = this.localDevice;
 		}
+		DiscoveryAgent discoveryAgent = localDevice.getDiscoveryAgent();
 		Map<String, ContactId> addresses = new HashMap<String, ContactId>();
 		Map<ContactId, String> uuids = new HashMap<ContactId, String>();
 		for(Entry<ContactId, TransportProperties> e : remote.entrySet()) {
@@ -245,6 +257,7 @@ class BluetoothPlugin implements StreamPlugin {
 		ContactListener listener = new ContactListener(discoveryAgent,
 				Collections.unmodifiableMap(addresses),
 				Collections.unmodifiableMap(uuids));
+		// FIXME: Avoid making alien calls with a lock held
 		synchronized(discoveryLock) {
 			try {
 				discoveryAgent.startInquiry(DiscoveryAgent.GIAC, listener);
@@ -335,17 +348,19 @@ class BluetoothPlugin implements StreamPlugin {
 	}
 
 	private void createInvitationConnection(ConnectionCallback c) {
-		DiscoveryAgent discoveryAgent;
+		LocalDevice localDevice;
 		synchronized(this) {
 			if(!running) return;
-			discoveryAgent = localDevice.getDiscoveryAgent();
+			localDevice = this.localDevice;
 		}
+		DiscoveryAgent discoveryAgent = localDevice.getDiscoveryAgent();
 		// Try to discover the other party until the invitation times out
 		long end = System.currentTimeMillis() + c.getTimeout();
 		String url = null;
 		while(url == null && System.currentTimeMillis() < end) {
 			InvitationListener listener = new InvitationListener(discoveryAgent,
 					c.getUuid());
+			// FIXME: Avoid making alien calls with a lock held
 			synchronized(discoveryLock) {
 				try {
 					discoveryAgent.startInquiry(DiscoveryAgent.GIAC, listener);
@@ -378,8 +393,8 @@ class BluetoothPlugin implements StreamPlugin {
 	private void bindInvitationSocket(final ConnectionCallback c) {
 		synchronized(this) {
 			if(!running) return;
-			makeDeviceDiscoverable();
 		}
+		makeDeviceDiscoverable();
 		String url = "btspp://localhost:" + c.getUuid() + ";name=RFCOMM";
 		final StreamConnectionNotifier scn;
 		try {
diff --git a/components/net/sf/briar/plugins/file/FilePlugin.java b/components/net/sf/briar/plugins/file/FilePlugin.java
index 23a6be7d82dc70129f0c88e98b9cb77fee0b5f76..66b8f572e7158a51c1e55e8cdf1ae74de3f89ed0 100644
--- a/components/net/sf/briar/plugins/file/FilePlugin.java
+++ b/components/net/sf/briar/plugins/file/FilePlugin.java
@@ -118,6 +118,7 @@ abstract class FilePlugin implements BatchPlugin {
 	private BatchTransportReader createInvitationReader(String filename,
 			long timeout) {
 		Collection<File> files;
+		// FIXME: Avoid making alien calls with a lock held
 		synchronized(listenerLock) {
 			// Find any matching files that have already arrived
 			files = findFilesByName(filename);
@@ -170,6 +171,7 @@ abstract class FilePlugin implements BatchPlugin {
 		public void run() {
 			String filename = file.getName();
 			if(isPossibleInvitationFilename(filename)) {
+				// FIXME: Avoid making alien calls with a lock held
 				synchronized(listenerLock) {
 					if(listener != null) listener.addFile(file);
 				}
diff --git a/components/net/sf/briar/plugins/file/PollingRemovableDriveMonitor.java b/components/net/sf/briar/plugins/file/PollingRemovableDriveMonitor.java
index 581010e88e5efddcc36b761296e9398dfc205e8e..689ce99dd71b1f14ce404a39b827bc6712e5255e 100644
--- a/components/net/sf/briar/plugins/file/PollingRemovableDriveMonitor.java
+++ b/components/net/sf/briar/plugins/file/PollingRemovableDriveMonitor.java
@@ -30,24 +30,31 @@ class PollingRemovableDriveMonitor implements RemovableDriveMonitor, Runnable {
 		this.pollingInterval = pollingInterval;
 	}
 
-	public synchronized void start(Callback callback) throws IOException {
-		if(running) throw new IllegalStateException();
-		running = true;
-		this.callback = callback;
+	public void start(Callback callback) throws IOException {
+		synchronized(this) {
+			assert !running;
+			assert this.callback == null;
+			assert exception == null;
+			running = true;
+			this.callback = callback;
+		}
 		pluginExecutor.execute(this);
 	}
 
-	public synchronized void stop() throws IOException {
-		if(!running) throw new IllegalStateException();
-		running = false;
-		if(exception != null) {
-			IOException e = exception;
+	public void stop() throws IOException {
+		IOException e;
+		synchronized(this) {
+			assert running;
+			assert callback != null;
+			running = false;
+			callback = null;
+			e = exception;
 			exception = null;
-			throw e;
 		}
 		synchronized(pollingLock) {
 			pollingLock.notifyAll();
 		}
+		if(e != null) throw e;
 	}
 
 	public void run() {
diff --git a/components/net/sf/briar/plugins/file/UnixRemovableDriveMonitor.java b/components/net/sf/briar/plugins/file/UnixRemovableDriveMonitor.java
index 18941b9a9ae7c23e9231709a344e0b06296ec66b..0a7d251d449b35b91c1689ae8a2e2df1dc6543bc 100644
--- a/components/net/sf/briar/plugins/file/UnixRemovableDriveMonitor.java
+++ b/components/net/sf/briar/plugins/file/UnixRemovableDriveMonitor.java
@@ -19,28 +19,42 @@ JNotifyListener {
 
 	protected abstract String[] getPathsToWatch();
 
-	public synchronized void start(Callback callback) throws IOException {
-		if(started) throw new IllegalStateException();
-		started = true;
-		this.callback = callback;
+	public void start(Callback callback) throws IOException {
+		List<Integer> watches = new ArrayList<Integer>();
 		int mask = JNotify.FILE_CREATED;
 		for(String path : getPathsToWatch()) {
 			if(new File(path).exists())
 				watches.add(JNotify.addWatch(path, mask, false, this));
 		}
+		synchronized(this) {
+			assert !started;
+			assert callback == null;
+			started = true;
+			this.callback = callback;
+			this.watches.addAll(watches);
+		}
 	}
 
-	public synchronized void stop() throws IOException {
-		if(!started) throw new IllegalStateException();
-		started = false;
-		callback = null;
+	public void stop() throws IOException {
+		List<Integer> watches;
+		synchronized(this) {
+			assert started;
+			assert callback != null;
+			started = false;
+			callback = null;
+			watches = new ArrayList<Integer>(this.watches);
+			this.watches.clear();
+		}
 		for(Integer w : watches) JNotify.removeWatch(w);
-		watches.clear();
 	}
 
-	public synchronized void fileCreated(int wd, String rootPath, String name) {
-		if(!started) throw new IllegalStateException();
-		callback.driveInserted(new File(rootPath + "/" + name));
+	public void fileCreated(int wd, String rootPath, String name) {
+		Callback callback;
+		synchronized(this) {
+			callback = this.callback;
+		}
+		if(callback != null)
+			callback.driveInserted(new File(rootPath + "/" + name));
 	}
 
 	public void fileDeleted(int wd, String rootPath, String name) {
diff --git a/components/net/sf/briar/plugins/socket/SocketPlugin.java b/components/net/sf/briar/plugins/socket/SocketPlugin.java
index c5001ef85f71d64b80ee531c17414530383bbac1..2c046478eabcf751891862074e2344fcaf2be1b2 100644
--- a/components/net/sf/briar/plugins/socket/SocketPlugin.java
+++ b/components/net/sf/briar/plugins/socket/SocketPlugin.java
@@ -118,7 +118,7 @@ abstract class SocketPlugin implements StreamPlugin {
 	public synchronized void stop() throws IOException {
 		running = false;
 		if(socket != null) {
-			socket.close();
+			tryToClose(socket);
 			socket = null;
 		}
 	}