diff --git a/api/net/sf/briar/api/lifecycle/ShutdownManager.java b/api/net/sf/briar/api/lifecycle/ShutdownManager.java
new file mode 100644
index 0000000000000000000000000000000000000000..8d8b0af405feb380b4947929e882efbda47d9721
--- /dev/null
+++ b/api/net/sf/briar/api/lifecycle/ShutdownManager.java
@@ -0,0 +1,16 @@
+package net.sf.briar.api.lifecycle;
+
+public interface ShutdownManager {
+
+	/**
+	 * Registers a hook to be run when the JVM shuts down and returns a handle
+	 * that can be used to remove the hook.
+	 */
+	int addShutdownHook(Runnable hook);
+
+	/**
+	 * Removes the shutdown hook identified by the given handle and returns
+	 * true if the hook was removed.
+	 */
+	boolean removeShutdownHook(int handle);
+}
diff --git a/build-common.xml b/build-common.xml
index 7d7504ff662d459ed455ea5edbf88f1ab3b98967..681fdb9a105be02cc5a47087ac0a33ac2dc637a8 100644
--- a/build-common.xml
+++ b/build-common.xml
@@ -21,6 +21,7 @@
 	</path>
 	<target name='clean'>
 		<delete dir='build'/>
+		<delete dir='test.tmp'/>
 	</target>
 	<target name='compile'>
 		<mkdir dir='build'/>
diff --git a/components/net/sf/briar/db/DatabaseComponentImpl.java b/components/net/sf/briar/db/DatabaseComponentImpl.java
index d8b2d5c8d40a4f8ae121d54a1ab7867d1264e48d..92420cca1de7ac407f22a6bd7a6111233dbce32a 100644
--- a/components/net/sf/briar/db/DatabaseComponentImpl.java
+++ b/components/net/sf/briar/db/DatabaseComponentImpl.java
@@ -16,6 +16,8 @@ import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.locks.ReentrantReadWriteLock;
+import java.util.logging.Level;
+import java.util.logging.Logger;
 
 import net.sf.briar.api.Bytes;
 import net.sf.briar.api.ContactId;
@@ -38,6 +40,7 @@ import net.sf.briar.api.db.event.RatingChangedEvent;
 import net.sf.briar.api.db.event.RemoteTransportsUpdatedEvent;
 import net.sf.briar.api.db.event.SubscriptionsUpdatedEvent;
 import net.sf.briar.api.db.event.TransportAddedEvent;
+import net.sf.briar.api.lifecycle.ShutdownManager;
 import net.sf.briar.api.protocol.Ack;
 import net.sf.briar.api.protocol.AuthorId;
 import net.sf.briar.api.protocol.Batch;
@@ -73,6 +76,9 @@ import com.google.inject.Inject;
 class DatabaseComponentImpl<T> implements DatabaseComponent,
 DatabaseCleaner.Callback {
 
+	private static final Logger LOG =
+		Logger.getLogger(DatabaseComponentImpl.class.getName());
+
 	/*
 	 * Locks must always be acquired in alphabetical order. See the Database
 	 * interface to find out which calls require which locks.
@@ -97,27 +103,61 @@ DatabaseCleaner.Callback {
 
 	private final Database<T> db;
 	private final DatabaseCleaner cleaner;
+	private final ShutdownManager shutdown;
 
 	private final List<DatabaseListener> listeners =
 		new ArrayList<DatabaseListener>(); // Locking: self
+
 	private final Object spaceLock = new Object();
 	private long bytesStoredSinceLastCheck = 0L; // Locking: spaceLock
 	private long timeOfLastCheck = 0L; // Locking: spaceLock
 
+	private final Object openCloseLock = new Object();
+	private boolean open = false; // Locking: openCloseLock;
+	private int shutdownHandle = -1; // Locking: openCloseLock;
+
 	@Inject
-	DatabaseComponentImpl(Database<T> db, DatabaseCleaner cleaner) {
+	DatabaseComponentImpl(Database<T> db, DatabaseCleaner cleaner,
+			ShutdownManager shutdown) {
 		this.db = db;
 		this.cleaner = cleaner;
+		this.shutdown = shutdown;
 	}
 
 	public void open(boolean resume) throws DbException, IOException {
-		db.open(resume);
-		cleaner.startCleaning(this, MAX_MS_BETWEEN_SPACE_CHECKS);
+		synchronized(openCloseLock) {
+			if(open) throw new IllegalStateException();
+			open = true;
+			db.open(resume);
+			cleaner.startCleaning(this, MAX_MS_BETWEEN_SPACE_CHECKS);
+			shutdownHandle = shutdown.addShutdownHook(new Runnable() {
+				public void run() {
+					try {
+						synchronized(openCloseLock) {
+							shutdownHandle = -1;
+							close();
+						}
+					} catch(DbException e) {
+						if(LOG.isLoggable(Level.WARNING))
+							LOG.warning(e.getMessage());
+					} catch(IOException e) {
+						if(LOG.isLoggable(Level.WARNING))
+							LOG.warning(e.getMessage());
+					}
+				}
+			});
+		}
 	}
 
 	public void close() throws DbException, IOException {
-		cleaner.stopCleaning();
-		db.close();
+		synchronized(openCloseLock) {
+			if(!open) return;
+			open = false;
+			if(shutdownHandle != -1)
+				shutdown.removeShutdownHook(shutdownHandle);
+			cleaner.stopCleaning();
+			db.close();
+		}
 	}
 
 	public void addListener(DatabaseListener d) {
diff --git a/components/net/sf/briar/db/DatabaseModule.java b/components/net/sf/briar/db/DatabaseModule.java
index 9df6352ecdad7be6c56b4ac7d66cbe3c7e2f725c..ab58a5efa595395db09bab9bc44133f9b5554447 100644
--- a/components/net/sf/briar/db/DatabaseModule.java
+++ b/components/net/sf/briar/db/DatabaseModule.java
@@ -8,6 +8,7 @@ import net.sf.briar.api.db.DatabaseComponent;
 import net.sf.briar.api.db.DatabaseDirectory;
 import net.sf.briar.api.db.DatabaseMaxSize;
 import net.sf.briar.api.db.DatabasePassword;
+import net.sf.briar.api.lifecycle.ShutdownManager;
 import net.sf.briar.api.protocol.GroupFactory;
 import net.sf.briar.api.transport.ConnectionContextFactory;
 import net.sf.briar.api.transport.ConnectionWindowFactory;
@@ -35,7 +36,7 @@ public class DatabaseModule extends AbstractModule {
 
 	@Provides @Singleton
 	DatabaseComponent getDatabaseComponent(Database<Connection> db,
-			DatabaseCleaner cleaner) {
-		return new DatabaseComponentImpl<Connection>(db, cleaner);
+			DatabaseCleaner cleaner, ShutdownManager shutdown) {
+		return new DatabaseComponentImpl<Connection>(db, cleaner, shutdown);
 	}
 }
diff --git a/components/net/sf/briar/lifecycle/LifecycleModule.java b/components/net/sf/briar/lifecycle/LifecycleModule.java
new file mode 100644
index 0000000000000000000000000000000000000000..241e41a09ae3963d87ceb47586e8d975744b3031
--- /dev/null
+++ b/components/net/sf/briar/lifecycle/LifecycleModule.java
@@ -0,0 +1,16 @@
+package net.sf.briar.lifecycle;
+
+import net.sf.briar.api.lifecycle.ShutdownManager;
+import net.sf.briar.util.OsUtils;
+
+import com.google.inject.AbstractModule;
+
+public class LifecycleModule extends AbstractModule {
+
+	@Override
+	protected void configure() {
+		if(OsUtils.isWindows())
+			bind(ShutdownManager.class).to(WindowsShutdownManagerImpl.class);
+		else bind(ShutdownManager.class).to(ShutdownManagerImpl.class);
+	}
+}
diff --git a/components/net/sf/briar/lifecycle/ShutdownManagerImpl.java b/components/net/sf/briar/lifecycle/ShutdownManagerImpl.java
new file mode 100644
index 0000000000000000000000000000000000000000..001f8a3f0a4a2cbf03770e1360d99e59ff7fc7b7
--- /dev/null
+++ b/components/net/sf/briar/lifecycle/ShutdownManagerImpl.java
@@ -0,0 +1,31 @@
+package net.sf.briar.lifecycle;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import net.sf.briar.api.lifecycle.ShutdownManager;
+
+class ShutdownManagerImpl implements ShutdownManager {
+
+	protected final Map<Integer, Thread> hooks; // Locking: this
+
+	private int nextHandle = 0; // Locking: this
+
+	ShutdownManagerImpl() {
+		hooks = new HashMap<Integer, Thread>();
+	}
+
+	public synchronized int addShutdownHook(Runnable runnable) {
+		int handle = nextHandle++;
+		Thread hook = new Thread(runnable);
+		hooks.put(handle, hook);
+		Runtime.getRuntime().addShutdownHook(hook);
+		return handle;
+	}
+
+	public synchronized boolean removeShutdownHook(int handle) {
+		Thread hook = hooks.remove(handle);
+		if(hook == null) return false;
+		else return Runtime.getRuntime().removeShutdownHook(hook);
+	}
+}
diff --git a/components/net/sf/briar/lifecycle/WindowsShutdownManagerImpl.java b/components/net/sf/briar/lifecycle/WindowsShutdownManagerImpl.java
new file mode 100644
index 0000000000000000000000000000000000000000..7d8939c340365038da773a0f0a5a3113c4153754
--- /dev/null
+++ b/components/net/sf/briar/lifecycle/WindowsShutdownManagerImpl.java
@@ -0,0 +1,142 @@
+package net.sf.briar.lifecycle;
+
+import java.awt.HeadlessException;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.swing.JFrame;
+
+import net.sf.briar.util.OsUtils;
+
+import com.sun.jna.Native;
+import com.sun.jna.platform.win32.WinDef.HWND;
+import com.sun.jna.platform.win32.WinDef.LPARAM;
+import com.sun.jna.platform.win32.WinDef.LRESULT;
+import com.sun.jna.platform.win32.WinDef.WPARAM;
+import com.sun.jna.win32.StdCallLibrary;
+import com.sun.jna.win32.StdCallLibrary.StdCallCallback;
+
+class WindowsShutdownManagerImpl extends ShutdownManagerImpl {
+
+	private static final Logger LOG =
+		Logger.getLogger(WindowsShutdownManagerImpl.class.getName());
+
+	private static final int WM_QUERYENDSESSION = 17;
+	private static final int WM_ENDSESSION = 22;
+	private static final int GWL_WNDPROC = -4;
+
+	private boolean initialised = false; // Locking: this
+
+	@Override
+	public synchronized int addShutdownHook(Runnable runnable) {
+		if(!initialised) initialise();
+		return super.addShutdownHook(new RunOnce(runnable));
+	}
+
+	// Locking: this
+	private void initialise() {
+		if(OsUtils.isWindows()) {
+			try {
+				HWND hwnd = new HWND();
+				hwnd.setPointer(Native.getComponentPointer(new JFrame()));
+				User32 u = (User32) Native.loadLibrary("user32", User32.class);
+				try {
+					// Load the 64-bit functions
+					setCallback64Bit(u, hwnd);
+				} catch(UnsatisfiedLinkError e) {
+					// Load the 32-bit functions
+					setCallback32Bit(u, hwnd);
+				}
+			} catch(HeadlessException e) {
+				if(LOG.isLoggable(Level.WARNING)) LOG.warning(e.getMessage());
+			} catch(UnsatisfiedLinkError e) {
+				if(LOG.isLoggable(Level.WARNING)) LOG.warning(e.getMessage());
+			}
+		} else {
+			if(LOG.isLoggable(Level.WARNING))
+				LOG.warning("Windows shutdown manager used on non-Windows OS");
+		}
+		initialised = true;
+	}
+
+	private void setCallback64Bit(final User32 user32, HWND hwnd) {
+		final WindowProc oldProc = user32.GetWindowLongPtrW(hwnd, GWL_WNDPROC);
+		WindowProc newProc = new WindowProc() {
+			public LRESULT callback(HWND hwnd, int msg, WPARAM wp, LPARAM lp) {
+				if(msg == WM_QUERYENDSESSION) {
+					// It's safe to delay returning from this message
+					runShutdownHooks();
+				} else if(msg == WM_ENDSESSION) {
+					// Return immediately or the JVM crashes on return
+				}
+				return user32.CallWindowProcPtrW(oldProc, hwnd, msg, wp, lp);
+			}
+		};
+		user32.SetWindowLongPtrW(hwnd, GWL_WNDPROC, newProc);
+	}
+
+	private void setCallback32Bit(final User32 user32, HWND hwnd) {
+		final WindowProc oldProc = user32.GetWindowLongW(hwnd, GWL_WNDPROC);
+		WindowProc newProc = new WindowProc() {
+			public LRESULT callback(HWND hwnd, int msg, WPARAM wp, LPARAM lp) {
+				if(msg == WM_QUERYENDSESSION) {
+					// It's safe to delay returning from this message
+					runShutdownHooks();
+				} else if(msg == WM_ENDSESSION) {
+					// Return immediately or the JVM crashes on return
+				}
+				return user32.CallWindowProcW(oldProc, hwnd, msg, wp, lp);
+			}
+		};
+		user32.SetWindowLongW(hwnd, GWL_WNDPROC, newProc);
+	}
+
+	// Package access for testing
+	synchronized void runShutdownHooks() {
+		// Start each hook in its own thread
+		for(Thread hook : hooks.values()) hook.start();
+		// Wait for all the hooks to finish
+		for(Thread hook : hooks.values()) {
+			try {
+				hook.join();
+			} catch(InterruptedException e) {
+				if(LOG.isLoggable(Level.WARNING)) LOG.warning(e.getMessage());
+			}
+		}
+	}
+
+	private static class RunOnce implements Runnable {
+
+		private final Runnable runnable;
+		private final AtomicBoolean called = new AtomicBoolean(false);
+
+		private RunOnce(Runnable runnable) {
+			this.runnable = runnable;
+		}
+
+		public void run() {
+			if(called.getAndSet(true)) return;
+			runnable.run();
+		}
+	}
+
+	private static interface User32 extends StdCallLibrary {
+
+		LRESULT CallWindowProcW(WindowProc oldProc, HWND hwnd, int msg,
+				WPARAM wp, LPARAM lp);
+		LRESULT CallWindowProcPtrW(WindowProc oldProc, HWND hwnd, int msg,
+				WPARAM wp, LPARAM lp);
+
+		WindowProc GetWindowLongW(HWND hwnd, int index);
+		WindowProc GetWindowLongPtrW(HWND hwnd, int index);
+
+		LRESULT SetWindowLongW(HWND hwnd, int index, WindowProc newProc);
+		LRESULT SetWindowLongPtrW(HWND hwnd, int index, WindowProc newProc);
+	}
+
+	private static interface WindowProc extends StdCallCallback {
+
+		public LRESULT callback(HWND hwnd, int msg, WPARAM wp, LPARAM lp);
+	}
+}
diff --git a/components/net/sf/briar/plugins/socket/LanSocketPlugin.java b/components/net/sf/briar/plugins/socket/LanSocketPlugin.java
index 4cd0d9efbd39bee518d059f21a3b7b936e76f78b..181a9b8ff3c73c69438cf164f36106976706c209 100644
--- a/components/net/sf/briar/plugins/socket/LanSocketPlugin.java
+++ b/components/net/sf/briar/plugins/socket/LanSocketPlugin.java
@@ -19,7 +19,7 @@ import net.sf.briar.api.transport.StreamTransportConnection;
 import net.sf.briar.util.ByteUtils;
 
 /** A socket plugin that supports exchanging invitations over a LAN. */
-public class LanSocketPlugin extends SimpleSocketPlugin {
+class LanSocketPlugin extends SimpleSocketPlugin {
 
 	private static final Logger LOG =
 		Logger.getLogger(LanSocketPlugin.class.getName());
diff --git a/components/net/sf/briar/transport/ConnectionDispatcherImpl.java b/components/net/sf/briar/transport/ConnectionDispatcherImpl.java
index adbb5ad45d85091287a93198c26eca1fae1f29da..54cce8ab0d0288a4aae340c87a9283cac7b17d6b 100644
--- a/components/net/sf/briar/transport/ConnectionDispatcherImpl.java
+++ b/components/net/sf/briar/transport/ConnectionDispatcherImpl.java
@@ -22,7 +22,7 @@ import net.sf.briar.api.transport.TransportConstants;
 
 import com.google.inject.Inject;
 
-public class ConnectionDispatcherImpl implements ConnectionDispatcher {
+class ConnectionDispatcherImpl implements ConnectionDispatcher {
 
 	private static final Logger LOG =
 		Logger.getLogger(ConnectionDispatcherImpl.class.getName());
diff --git a/components/net/sf/briar/transport/ConnectionRecogniserImpl.java b/components/net/sf/briar/transport/ConnectionRecogniserImpl.java
index 940b37c0934cf8c75cb62a77f384d74c1dd0aab9..34eacc8684d855672cf81416bb4bd3f8de2af447 100644
--- a/components/net/sf/briar/transport/ConnectionRecogniserImpl.java
+++ b/components/net/sf/briar/transport/ConnectionRecogniserImpl.java
@@ -28,6 +28,7 @@ import net.sf.briar.api.db.event.DatabaseEvent;
 import net.sf.briar.api.db.event.DatabaseListener;
 import net.sf.briar.api.db.event.RemoteTransportsUpdatedEvent;
 import net.sf.briar.api.db.event.TransportAddedEvent;
+import net.sf.briar.api.lifecycle.ShutdownManager;
 import net.sf.briar.api.protocol.Transport;
 import net.sf.briar.api.protocol.TransportId;
 import net.sf.briar.api.protocol.TransportIndex;
@@ -46,6 +47,7 @@ DatabaseListener {
 	private final CryptoComponent crypto;
 	private final DatabaseComponent db;
 	private final Executor executor;
+	private final ShutdownManager shutdown;
 	private final Cipher ivCipher; // Locking: this
 	private final Map<Bytes, Context> expected; // Locking: this
 
@@ -53,10 +55,11 @@ DatabaseListener {
 
 	@Inject
 	ConnectionRecogniserImpl(CryptoComponent crypto, DatabaseComponent db,
-			Executor executor) {
+			Executor executor, ShutdownManager shutdown) {
 		this.crypto = crypto;
 		this.db = db;
 		this.executor = executor;
+		this.shutdown = shutdown;
 		ivCipher = crypto.getIvCipher();
 		expected = new HashMap<Bytes, Context>();
 		db.addListener(this);
@@ -64,8 +67,8 @@ DatabaseListener {
 
 	// Locking: this
 	private void initialise() throws DbException {
-		Runtime.getRuntime().addShutdownHook(new Thread() {
-			@Override
+		assert !initialised;
+		shutdown.addShutdownHook(new Runnable() {
 			public void run() {
 				eraseSecrets();
 			}
diff --git a/components/net/sf/briar/transport/stream/IncomingStreamConnection.java b/components/net/sf/briar/transport/stream/IncomingStreamConnection.java
index d3da63f21c29ac3d4e241d9a0bf0589ccb6ba363..994693d0b79ecbfdeaf856af24965e7693614728 100644
--- a/components/net/sf/briar/transport/stream/IncomingStreamConnection.java
+++ b/components/net/sf/briar/transport/stream/IncomingStreamConnection.java
@@ -13,7 +13,7 @@ import net.sf.briar.api.transport.ConnectionWriter;
 import net.sf.briar.api.transport.ConnectionWriterFactory;
 import net.sf.briar.api.transport.StreamTransportConnection;
 
-public class IncomingStreamConnection extends StreamConnection {
+class IncomingStreamConnection extends StreamConnection {
 
 	private final ConnectionContext ctx;
 	private final byte[] encryptedIv;
diff --git a/components/net/sf/briar/transport/stream/OutgoingStreamConnection.java b/components/net/sf/briar/transport/stream/OutgoingStreamConnection.java
index 178190e2a131e79f12e750942f97bd824cf6ebcf..ba37e28def8261a5ece29bcdc17cac8adb950242 100644
--- a/components/net/sf/briar/transport/stream/OutgoingStreamConnection.java
+++ b/components/net/sf/briar/transport/stream/OutgoingStreamConnection.java
@@ -15,7 +15,7 @@ import net.sf.briar.api.transport.ConnectionWriter;
 import net.sf.briar.api.transport.ConnectionWriterFactory;
 import net.sf.briar.api.transport.StreamTransportConnection;
 
-public class OutgoingStreamConnection extends StreamConnection {
+class OutgoingStreamConnection extends StreamConnection {
 
 	private final TransportIndex transportIndex;
 
diff --git a/components/net/sf/briar/transport/stream/StreamConnectionFactoryImpl.java b/components/net/sf/briar/transport/stream/StreamConnectionFactoryImpl.java
index cfaf8fe74718a80a8f7d954af5d98fc14ee43ee3..514a702302eb119ded31b24a2e62614c5aaee246 100644
--- a/components/net/sf/briar/transport/stream/StreamConnectionFactoryImpl.java
+++ b/components/net/sf/briar/transport/stream/StreamConnectionFactoryImpl.java
@@ -13,7 +13,7 @@ import net.sf.briar.api.transport.StreamTransportConnection;
 
 import com.google.inject.Inject;
 
-public class StreamConnectionFactoryImpl implements StreamConnectionFactory {
+class StreamConnectionFactoryImpl implements StreamConnectionFactory {
 
 	private final ConnectionReaderFactory connReaderFactory;
 	private final ConnectionWriterFactory connWriterFactory;
diff --git a/test/build.xml b/test/build.xml
index 2644843b78feab0845136cee55befdd64c8bc34b..b3c7132f56dd40102bc7cb3a674430df9239ba7e 100644
--- a/test/build.xml
+++ b/test/build.xml
@@ -26,6 +26,7 @@
 			<test name='net.sf.briar.i18n.FontManagerTest'/>
 			<test name='net.sf.briar.i18n.I18nTest'/>
 			<test name='net.sf.briar.invitation.InvitationWorkerTest'/>
+			<test name='net.sf.briar.lifecycle.ShutdownManagerImplTest'/>
 			<test name='net.sf.briar.plugins.PluginManagerImplTest'/>
 			<test name='net.sf.briar.plugins.file.LinuxRemovableDriveFinderTest'/>
 			<test name='net.sf.briar.plugins.file.MacRemovableDriveFinderTest'/>
diff --git a/test/net/sf/briar/ProtocolIntegrationTest.java b/test/net/sf/briar/ProtocolIntegrationTest.java
index cd0ad582f35525d2897c2c14fdade3283001f7ac..1ed4eb0fb730b1bd17135afbd85f378887c9f4d4 100644
--- a/test/net/sf/briar/ProtocolIntegrationTest.java
+++ b/test/net/sf/briar/ProtocolIntegrationTest.java
@@ -54,6 +54,7 @@ import net.sf.briar.api.transport.ConnectionWriter;
 import net.sf.briar.api.transport.ConnectionWriterFactory;
 import net.sf.briar.crypto.CryptoModule;
 import net.sf.briar.db.DatabaseModule;
+import net.sf.briar.lifecycle.LifecycleModule;
 import net.sf.briar.protocol.ProtocolModule;
 import net.sf.briar.protocol.writers.ProtocolWritersModule;
 import net.sf.briar.serial.SerialModule;
@@ -102,10 +103,11 @@ public class ProtocolIntegrationTest extends TestCase {
 			}
 		};
 		Injector i = Guice.createInjector(testModule, new CryptoModule(),
-				new DatabaseModule(), new ProtocolModule(),
-				new ProtocolWritersModule(), new SerialModule(),
-				new TestDatabaseModule(), new TransportBatchModule(),
-				new TransportModule(), new TransportStreamModule());
+				new DatabaseModule(), new LifecycleModule(),
+				new ProtocolModule(), new ProtocolWritersModule(),
+				new SerialModule(), new TestDatabaseModule(),
+				new TransportBatchModule(), new TransportModule(),
+				new TransportStreamModule());
 		connectionContextFactory =
 			i.getInstance(ConnectionContextFactory.class);
 		connectionReaderFactory = i.getInstance(ConnectionReaderFactory.class);
diff --git a/test/net/sf/briar/db/DatabaseComponentImplTest.java b/test/net/sf/briar/db/DatabaseComponentImplTest.java
index da67ef8b4d4855df0777a2ddcf371faddc548d3f..0a2c3f612c0b39b65dd90e4c5a240419a1bfbc11 100644
--- a/test/net/sf/briar/db/DatabaseComponentImplTest.java
+++ b/test/net/sf/briar/db/DatabaseComponentImplTest.java
@@ -7,6 +7,7 @@ import java.util.Collections;
 
 import net.sf.briar.api.db.DatabaseComponent;
 import net.sf.briar.api.db.DbException;
+import net.sf.briar.api.lifecycle.ShutdownManager;
 import net.sf.briar.db.DatabaseCleaner.Callback;
 
 import org.jmock.Expectations;
@@ -25,11 +26,12 @@ public class DatabaseComponentImplTest extends DatabaseComponentTest {
 		@SuppressWarnings("unchecked")
 		final Database<Object> database = context.mock(Database.class);
 		final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class);
+		final ShutdownManager shutdown = context.mock(ShutdownManager.class);
 		context.checking(new Expectations() {{
 			oneOf(database).getFreeSpace();
 			will(returnValue(MIN_FREE_SPACE));
 		}});
-		Callback db = createDatabaseComponentImpl(database, cleaner);
+		Callback db = createDatabaseComponentImpl(database, cleaner, shutdown);
 
 		db.checkFreeSpaceAndClean();
 
@@ -42,6 +44,7 @@ public class DatabaseComponentImplTest extends DatabaseComponentTest {
 		@SuppressWarnings("unchecked")
 		final Database<Object> database = context.mock(Database.class);
 		final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class);
+		final ShutdownManager shutdown = context.mock(ShutdownManager.class);
 		context.checking(new Expectations() {{
 			oneOf(database).getFreeSpace();
 			will(returnValue(MIN_FREE_SPACE - 1));
@@ -54,7 +57,7 @@ public class DatabaseComponentImplTest extends DatabaseComponentTest {
 			oneOf(database).getFreeSpace();
 			will(returnValue(MIN_FREE_SPACE));
 		}});
-		Callback db = createDatabaseComponentImpl(database, cleaner);
+		Callback db = createDatabaseComponentImpl(database, cleaner, shutdown);
 
 		db.checkFreeSpaceAndClean();
 
@@ -68,6 +71,7 @@ public class DatabaseComponentImplTest extends DatabaseComponentTest {
 		@SuppressWarnings("unchecked")
 		final Database<Object> database = context.mock(Database.class);
 		final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class);
+		final ShutdownManager shutdown = context.mock(ShutdownManager.class);
 		context.checking(new Expectations() {{
 			oneOf(database).getFreeSpace();
 			will(returnValue(MIN_FREE_SPACE - 1));
@@ -82,7 +86,7 @@ public class DatabaseComponentImplTest extends DatabaseComponentTest {
 			oneOf(database).getFreeSpace();
 			will(returnValue(MIN_FREE_SPACE));
 		}});
-		Callback db = createDatabaseComponentImpl(database, cleaner);
+		Callback db = createDatabaseComponentImpl(database, cleaner, shutdown);
 
 		db.checkFreeSpaceAndClean();
 
@@ -96,6 +100,7 @@ public class DatabaseComponentImplTest extends DatabaseComponentTest {
 		@SuppressWarnings("unchecked")
 		final Database<Object> database = context.mock(Database.class);
 		final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class);
+		final ShutdownManager shutdown = context.mock(ShutdownManager.class);
 		context.checking(new Expectations() {{
 			oneOf(database).getFreeSpace();
 			will(returnValue(MIN_FREE_SPACE - 1));
@@ -112,7 +117,7 @@ public class DatabaseComponentImplTest extends DatabaseComponentTest {
 			oneOf(database).getFreeSpace();
 			will(returnValue(MIN_FREE_SPACE));
 		}});
-		Callback db = createDatabaseComponentImpl(database, cleaner);
+		Callback db = createDatabaseComponentImpl(database, cleaner, shutdown);
 
 		db.checkFreeSpaceAndClean();
 
@@ -121,12 +126,14 @@ public class DatabaseComponentImplTest extends DatabaseComponentTest {
 
 	@Override
 	protected <T> DatabaseComponent createDatabaseComponent(
-			Database<T> database, DatabaseCleaner cleaner) {
-		return createDatabaseComponentImpl(database, cleaner);
+			Database<T> database, DatabaseCleaner cleaner,
+			ShutdownManager shutdown) {
+		return createDatabaseComponentImpl(database, cleaner, shutdown);
 	}
 
 	private <T> DatabaseComponentImpl<T> createDatabaseComponentImpl(
-			Database<T> database, DatabaseCleaner cleaner) {
-		return new DatabaseComponentImpl<T>(database, cleaner);
+			Database<T> database, DatabaseCleaner cleaner,
+			ShutdownManager shutdown) {
+		return new DatabaseComponentImpl<T>(database, cleaner, shutdown);
 	}
 }
diff --git a/test/net/sf/briar/db/DatabaseComponentTest.java b/test/net/sf/briar/db/DatabaseComponentTest.java
index 58c95b8a92d7710259d100fb4ba5249d5cd694f2..e17b18ed1aebcf201e7c01be909ce57a923b0151 100644
--- a/test/net/sf/briar/db/DatabaseComponentTest.java
+++ b/test/net/sf/briar/db/DatabaseComponentTest.java
@@ -22,6 +22,7 @@ import net.sf.briar.api.db.event.DatabaseListener;
 import net.sf.briar.api.db.event.MessagesAddedEvent;
 import net.sf.briar.api.db.event.RatingChangedEvent;
 import net.sf.briar.api.db.event.TransportAddedEvent;
+import net.sf.briar.api.lifecycle.ShutdownManager;
 import net.sf.briar.api.protocol.Ack;
 import net.sf.briar.api.protocol.AuthorId;
 import net.sf.briar.api.protocol.Batch;
@@ -103,14 +104,17 @@ public abstract class DatabaseComponentTest extends TestCase {
 	}
 
 	protected abstract <T> DatabaseComponent createDatabaseComponent(
-			Database<T> database, DatabaseCleaner cleaner);
+			Database<T> database, DatabaseCleaner cleaner,
+			ShutdownManager shutdown);
 
 	@Test
 	@SuppressWarnings("unchecked")
 	public void testSimpleCalls() throws Exception {
+		final int shutdownHandle = 12345;
 		Mockery context = new Mockery();
 		final Database<Object> database = context.mock(Database.class);
 		final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class);
+		final ShutdownManager shutdown = context.mock(ShutdownManager.class);
 		final ConnectionWindow connectionWindow =
 			context.mock(ConnectionWindow.class);
 		final Group group = context.mock(Group.class);
@@ -124,6 +128,8 @@ public abstract class DatabaseComponentTest extends TestCase {
 			oneOf(cleaner).startCleaning(
 					with(any(DatabaseCleaner.Callback.class)),
 					with(any(long.class)));
+			oneOf(shutdown).addShutdownHook(with(any(Runnable.class)));
+			will(returnValue(shutdownHandle));
 			// getRating(authorId)
 			oneOf(database).getRating(txn, authorId);
 			will(returnValue(Rating.UNRATED));
@@ -189,10 +195,12 @@ public abstract class DatabaseComponentTest extends TestCase {
 			oneOf(database).removeContact(txn, contactId);
 			oneOf(listener).eventOccurred(with(any(ContactRemovedEvent.class)));
 			// close()
+			oneOf(shutdown).removeShutdownHook(shutdownHandle);
 			oneOf(cleaner).stopCleaning();
 			oneOf(database).close();
 		}});
-		DatabaseComponent db = createDatabaseComponent(database, cleaner);
+		DatabaseComponent db = createDatabaseComponent(database, cleaner,
+				shutdown);
 
 		db.open(false);
 		db.addListener(listener);
@@ -224,6 +232,7 @@ public abstract class DatabaseComponentTest extends TestCase {
 		@SuppressWarnings("unchecked")
 		final Database<Object> database = context.mock(Database.class);
 		final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class);
+		final ShutdownManager shutdown = context.mock(ShutdownManager.class);
 		context.checking(new Expectations() {{
 			// setRating(authorId, Rating.GOOD)
 			allowing(database).startTransaction();
@@ -241,7 +250,8 @@ public abstract class DatabaseComponentTest extends TestCase {
 			will(returnValue(null));
 			oneOf(database).commitTransaction(txn);
 		}});
-		DatabaseComponent db = createDatabaseComponent(database, cleaner);
+		DatabaseComponent db = createDatabaseComponent(database, cleaner,
+				shutdown);
 
 		db.setRating(authorId, Rating.GOOD);
 
@@ -254,6 +264,7 @@ public abstract class DatabaseComponentTest extends TestCase {
 		@SuppressWarnings("unchecked")
 		final Database<Object> database = context.mock(Database.class);
 		final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class);
+		final ShutdownManager shutdown = context.mock(ShutdownManager.class);
 		context.checking(new Expectations() {{
 			// setRating(authorId, Rating.GOOD)
 			oneOf(database).startTransaction();
@@ -275,7 +286,8 @@ public abstract class DatabaseComponentTest extends TestCase {
 			oneOf(database).setSendability(txn, parentId, 2);
 			oneOf(database).commitTransaction(txn);
 		}});
-		DatabaseComponent db = createDatabaseComponent(database, cleaner);
+		DatabaseComponent db = createDatabaseComponent(database, cleaner,
+				shutdown);
 
 		db.setRating(authorId, Rating.GOOD);
 
@@ -289,6 +301,7 @@ public abstract class DatabaseComponentTest extends TestCase {
 		@SuppressWarnings("unchecked")
 		final Database<Object> database = context.mock(Database.class);
 		final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class);
+		final ShutdownManager shutdown = context.mock(ShutdownManager.class);
 		context.checking(new Expectations() {{
 			// setRating(authorId, Rating.GOOD)
 			oneOf(database).startTransaction();
@@ -313,7 +326,8 @@ public abstract class DatabaseComponentTest extends TestCase {
 			will(returnValue(null));
 			oneOf(database).commitTransaction(txn);
 		}});
-		DatabaseComponent db = createDatabaseComponent(database, cleaner);
+		DatabaseComponent db = createDatabaseComponent(database, cleaner,
+				shutdown);
 
 		db.setRating(authorId, Rating.GOOD);
 
@@ -327,6 +341,7 @@ public abstract class DatabaseComponentTest extends TestCase {
 		@SuppressWarnings("unchecked")
 		final Database<Object> database = context.mock(Database.class);
 		final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class);
+		final ShutdownManager shutdown = context.mock(ShutdownManager.class);
 		context.checking(new Expectations() {{
 			// addLocalGroupMessage(message)
 			oneOf(database).startTransaction();
@@ -335,7 +350,8 @@ public abstract class DatabaseComponentTest extends TestCase {
 			will(returnValue(false));
 			oneOf(database).commitTransaction(txn);
 		}});
-		DatabaseComponent db = createDatabaseComponent(database, cleaner);
+		DatabaseComponent db = createDatabaseComponent(database, cleaner,
+				shutdown);
 
 		db.addLocalGroupMessage(message);
 
@@ -348,6 +364,7 @@ public abstract class DatabaseComponentTest extends TestCase {
 		@SuppressWarnings("unchecked")
 		final Database<Object> database = context.mock(Database.class);
 		final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class);
+		final ShutdownManager shutdown = context.mock(ShutdownManager.class);
 		context.checking(new Expectations() {{
 			// addLocalGroupMessage(message)
 			oneOf(database).startTransaction();
@@ -358,7 +375,8 @@ public abstract class DatabaseComponentTest extends TestCase {
 			will(returnValue(false));
 			oneOf(database).commitTransaction(txn);
 		}});
-		DatabaseComponent db = createDatabaseComponent(database, cleaner);
+		DatabaseComponent db = createDatabaseComponent(database, cleaner,
+				shutdown);
 
 		db.addLocalGroupMessage(message);
 
@@ -371,6 +389,7 @@ public abstract class DatabaseComponentTest extends TestCase {
 		@SuppressWarnings("unchecked")
 		final Database<Object> database = context.mock(Database.class);
 		final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class);
+		final ShutdownManager shutdown = context.mock(ShutdownManager.class);
 		context.checking(new Expectations() {{
 			// addLocalGroupMessage(message)
 			oneOf(database).startTransaction();
@@ -390,7 +409,8 @@ public abstract class DatabaseComponentTest extends TestCase {
 			oneOf(database).setSendability(txn, messageId, 0);
 			oneOf(database).commitTransaction(txn);
 		}});
-		DatabaseComponent db = createDatabaseComponent(database, cleaner);
+		DatabaseComponent db = createDatabaseComponent(database, cleaner,
+				shutdown);
 
 		db.addLocalGroupMessage(message);
 
@@ -404,6 +424,7 @@ public abstract class DatabaseComponentTest extends TestCase {
 		@SuppressWarnings("unchecked")
 		final Database<Object> database = context.mock(Database.class);
 		final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class);
+		final ShutdownManager shutdown = context.mock(ShutdownManager.class);
 		context.checking(new Expectations() {{
 			// addLocalGroupMessage(message)
 			oneOf(database).startTransaction();
@@ -426,7 +447,8 @@ public abstract class DatabaseComponentTest extends TestCase {
 			will(returnValue(null));
 			oneOf(database).commitTransaction(txn);
 		}});
-		DatabaseComponent db = createDatabaseComponent(database, cleaner);
+		DatabaseComponent db = createDatabaseComponent(database, cleaner,
+				shutdown);
 
 		db.addLocalGroupMessage(message);
 
@@ -439,6 +461,7 @@ public abstract class DatabaseComponentTest extends TestCase {
 		@SuppressWarnings("unchecked")
 		final Database<Object> database = context.mock(Database.class);
 		final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class);
+		final ShutdownManager shutdown = context.mock(ShutdownManager.class);
 		context.checking(new Expectations() {{
 			allowing(database).startTransaction();
 			will(returnValue(txn));
@@ -449,7 +472,8 @@ public abstract class DatabaseComponentTest extends TestCase {
 			oneOf(database).addPrivateMessage(txn, privateMessage, contactId);
 			will(returnValue(false));
 		}});
-		DatabaseComponent db = createDatabaseComponent(database, cleaner);
+		DatabaseComponent db = createDatabaseComponent(database, cleaner,
+				shutdown);
 
 		db.addLocalPrivateMessage(privateMessage, contactId);
 
@@ -462,6 +486,7 @@ public abstract class DatabaseComponentTest extends TestCase {
 		@SuppressWarnings("unchecked")
 		final Database<Object> database = context.mock(Database.class);
 		final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class);
+		final ShutdownManager shutdown = context.mock(ShutdownManager.class);
 		context.checking(new Expectations() {{
 			allowing(database).startTransaction();
 			will(returnValue(txn));
@@ -473,7 +498,8 @@ public abstract class DatabaseComponentTest extends TestCase {
 			will(returnValue(true));
 			oneOf(database).setStatus(txn, contactId, messageId, Status.NEW);
 		}});
-		DatabaseComponent db = createDatabaseComponent(database, cleaner);
+		DatabaseComponent db = createDatabaseComponent(database, cleaner,
+				shutdown);
 
 		db.addLocalPrivateMessage(privateMessage, contactId);
 
@@ -487,6 +513,7 @@ public abstract class DatabaseComponentTest extends TestCase {
 		@SuppressWarnings("unchecked")
 		final Database<Object> database = context.mock(Database.class);
 		final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class);
+		final ShutdownManager shutdown = context.mock(ShutdownManager.class);
 		final AckWriter ackWriter = context.mock(AckWriter.class);
 		final BatchWriter batchWriter = context.mock(BatchWriter.class);
 		final OfferWriter offerWriter = context.mock(OfferWriter.class);
@@ -510,7 +537,8 @@ public abstract class DatabaseComponentTest extends TestCase {
 			will(returnValue(false));
 			exactly(19).of(database).commitTransaction(txn);
 		}});
-		DatabaseComponent db = createDatabaseComponent(database, cleaner);
+		DatabaseComponent db = createDatabaseComponent(database, cleaner,
+				shutdown);
 
 		try {
 			db.addLocalPrivateMessage(privateMessage, contactId);
@@ -621,6 +649,7 @@ public abstract class DatabaseComponentTest extends TestCase {
 		@SuppressWarnings("unchecked")
 		final Database<Object> database = context.mock(Database.class);
 		final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class);
+		final ShutdownManager shutdown = context.mock(ShutdownManager.class);
 		final AckWriter ackWriter = context.mock(AckWriter.class);
 		context.checking(new Expectations() {{
 			allowing(database).startTransaction();
@@ -641,7 +670,8 @@ public abstract class DatabaseComponentTest extends TestCase {
 			oneOf(database).removeBatchesToAck(txn, contactId,
 					Collections.singletonList(batchId));
 		}});
-		DatabaseComponent db = createDatabaseComponent(database, cleaner);
+		DatabaseComponent db = createDatabaseComponent(database, cleaner,
+				shutdown);
 
 		db.generateAck(contactId, ackWriter);
 
@@ -659,6 +689,7 @@ public abstract class DatabaseComponentTest extends TestCase {
 		@SuppressWarnings("unchecked")
 		final Database<Object> database = context.mock(Database.class);
 		final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class);
+		final ShutdownManager shutdown = context.mock(ShutdownManager.class);
 		final BatchWriter batchWriter = context.mock(BatchWriter.class);
 		context.checking(new Expectations() {{
 			allowing(database).startTransaction();
@@ -688,7 +719,8 @@ public abstract class DatabaseComponentTest extends TestCase {
 			oneOf(database).addOutstandingBatch(txn, contactId, batchId,
 					sendable);
 		}});
-		DatabaseComponent db = createDatabaseComponent(database, cleaner);
+		DatabaseComponent db = createDatabaseComponent(database, cleaner,
+				shutdown);
 
 		db.generateBatch(contactId, batchWriter);
 
@@ -708,6 +740,7 @@ public abstract class DatabaseComponentTest extends TestCase {
 		@SuppressWarnings("unchecked")
 		final Database<Object> database = context.mock(Database.class);
 		final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class);
+		final ShutdownManager shutdown = context.mock(ShutdownManager.class);
 		final BatchWriter batchWriter = context.mock(BatchWriter.class);
 		context.checking(new Expectations() {{
 			allowing(database).startTransaction();
@@ -734,7 +767,8 @@ public abstract class DatabaseComponentTest extends TestCase {
 			oneOf(database).addOutstandingBatch(txn, contactId, batchId,
 					Collections.singletonList(messageId1));
 		}});
-		DatabaseComponent db = createDatabaseComponent(database, cleaner);
+		DatabaseComponent db = createDatabaseComponent(database, cleaner,
+				shutdown);
 
 		db.generateBatch(contactId, batchWriter, requested);
 
@@ -751,6 +785,7 @@ public abstract class DatabaseComponentTest extends TestCase {
 		@SuppressWarnings("unchecked")
 		final Database<Object> database = context.mock(Database.class);
 		final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class);
+		final ShutdownManager shutdown = context.mock(ShutdownManager.class);
 		final OfferWriter offerWriter = context.mock(OfferWriter.class);
 		context.checking(new Expectations() {{
 			allowing(database).startTransaction();
@@ -768,7 +803,8 @@ public abstract class DatabaseComponentTest extends TestCase {
 			will(returnValue(false));
 			oneOf(offerWriter).finish();
 		}});
-		DatabaseComponent db = createDatabaseComponent(database, cleaner);
+		DatabaseComponent db = createDatabaseComponent(database, cleaner,
+				shutdown);
 
 		assertEquals(Collections.singletonList(messageId),
 				db.generateOffer(contactId, offerWriter));
@@ -783,6 +819,7 @@ public abstract class DatabaseComponentTest extends TestCase {
 		@SuppressWarnings("unchecked")
 		final Database<Object> database = context.mock(Database.class);
 		final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class);
+		final ShutdownManager shutdown = context.mock(ShutdownManager.class);
 		final SubscriptionUpdateWriter subscriptionUpdateWriter =
 			context.mock(SubscriptionUpdateWriter.class);
 		context.checking(new Expectations() {{
@@ -797,7 +834,8 @@ public abstract class DatabaseComponentTest extends TestCase {
 			oneOf(database).getSubscriptionsSent(txn, contactId);
 			will(returnValue(now));
 		}});
-		DatabaseComponent db = createDatabaseComponent(database, cleaner);
+		DatabaseComponent db = createDatabaseComponent(database, cleaner,
+				shutdown);
 
 		db.generateSubscriptionUpdate(contactId, subscriptionUpdateWriter);
 
@@ -814,6 +852,7 @@ public abstract class DatabaseComponentTest extends TestCase {
 		@SuppressWarnings("unchecked")
 		final Database<Object> database = context.mock(Database.class);
 		final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class);
+		final ShutdownManager shutdown = context.mock(ShutdownManager.class);
 		final SubscriptionUpdateWriter subscriptionUpdateWriter =
 			context.mock(SubscriptionUpdateWriter.class);
 		context.checking(new Expectations() {{
@@ -837,7 +876,8 @@ public abstract class DatabaseComponentTest extends TestCase {
 					with(Collections.singletonMap(group, 0L)),
 					with(any(long.class)));
 		}});
-		DatabaseComponent db = createDatabaseComponent(database, cleaner);
+		DatabaseComponent db = createDatabaseComponent(database, cleaner,
+				shutdown);
 
 		db.generateSubscriptionUpdate(contactId, subscriptionUpdateWriter);
 
@@ -851,6 +891,7 @@ public abstract class DatabaseComponentTest extends TestCase {
 		@SuppressWarnings("unchecked")
 		final Database<Object> database = context.mock(Database.class);
 		final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class);
+		final ShutdownManager shutdown = context.mock(ShutdownManager.class);
 		final TransportUpdateWriter transportUpdateWriter =
 			context.mock(TransportUpdateWriter.class);
 		context.checking(new Expectations() {{
@@ -865,7 +906,8 @@ public abstract class DatabaseComponentTest extends TestCase {
 			oneOf(database).getTransportsSent(txn, contactId);
 			will(returnValue(now));
 		}});
-		DatabaseComponent db = createDatabaseComponent(database, cleaner);
+		DatabaseComponent db = createDatabaseComponent(database, cleaner,
+				shutdown);
 
 		db.generateTransportUpdate(contactId, transportUpdateWriter);
 
@@ -882,6 +924,7 @@ public abstract class DatabaseComponentTest extends TestCase {
 		@SuppressWarnings("unchecked")
 		final Database<Object> database = context.mock(Database.class);
 		final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class);
+		final ShutdownManager shutdown = context.mock(ShutdownManager.class);
 		final TransportUpdateWriter transportUpdateWriter =
 			context.mock(TransportUpdateWriter.class);
 		context.checking(new Expectations() {{
@@ -904,7 +947,8 @@ public abstract class DatabaseComponentTest extends TestCase {
 			oneOf(transportUpdateWriter).writeTransports(with(transports),
 					with(any(long.class)));
 		}});
-		DatabaseComponent db = createDatabaseComponent(database, cleaner);
+		DatabaseComponent db = createDatabaseComponent(database, cleaner,
+				shutdown);
 
 		db.generateTransportUpdate(contactId, transportUpdateWriter);
 
@@ -918,6 +962,7 @@ public abstract class DatabaseComponentTest extends TestCase {
 		@SuppressWarnings("unchecked")
 		final Database<Object> database = context.mock(Database.class);
 		final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class);
+		final ShutdownManager shutdown = context.mock(ShutdownManager.class);
 		final Ack ack = context.mock(Ack.class);
 		context.checking(new Expectations() {{
 			allowing(database).startTransaction();
@@ -934,7 +979,8 @@ public abstract class DatabaseComponentTest extends TestCase {
 			will(returnValue(Collections.singletonList(batchId1)));
 			oneOf(database).removeLostBatch(txn, contactId, batchId1);
 		}});
-		DatabaseComponent db = createDatabaseComponent(database, cleaner);
+		DatabaseComponent db = createDatabaseComponent(database, cleaner,
+				shutdown);
 
 		db.receiveAck(contactId, ack);
 
@@ -947,6 +993,7 @@ public abstract class DatabaseComponentTest extends TestCase {
 		@SuppressWarnings("unchecked")
 		final Database<Object> database = context.mock(Database.class);
 		final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class);
+		final ShutdownManager shutdown = context.mock(ShutdownManager.class);
 		final Batch batch = context.mock(Batch.class);
 		context.checking(new Expectations() {{
 			allowing(database).startTransaction();
@@ -965,7 +1012,8 @@ public abstract class DatabaseComponentTest extends TestCase {
 			will(returnValue(batchId));
 			oneOf(database).addBatchToAck(txn, contactId, batchId);
 		}});
-		DatabaseComponent db = createDatabaseComponent(database, cleaner);
+		DatabaseComponent db = createDatabaseComponent(database, cleaner,
+				shutdown);
 
 		db.receiveBatch(contactId, batch);
 
@@ -978,6 +1026,7 @@ public abstract class DatabaseComponentTest extends TestCase {
 		@SuppressWarnings("unchecked")
 		final Database<Object> database = context.mock(Database.class);
 		final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class);
+		final ShutdownManager shutdown = context.mock(ShutdownManager.class);
 		final Batch batch = context.mock(Batch.class);
 		context.checking(new Expectations() {{
 			allowing(database).startTransaction();
@@ -995,7 +1044,8 @@ public abstract class DatabaseComponentTest extends TestCase {
 			will(returnValue(batchId));
 			oneOf(database).addBatchToAck(txn, contactId, batchId);
 		}});
-		DatabaseComponent db = createDatabaseComponent(database, cleaner);
+		DatabaseComponent db = createDatabaseComponent(database, cleaner,
+				shutdown);
 
 		db.receiveBatch(contactId, batch);
 
@@ -1009,6 +1059,7 @@ public abstract class DatabaseComponentTest extends TestCase {
 		@SuppressWarnings("unchecked")
 		final Database<Object> database = context.mock(Database.class);
 		final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class);
+		final ShutdownManager shutdown = context.mock(ShutdownManager.class);
 		final Batch batch = context.mock(Batch.class);
 		context.checking(new Expectations() {{
 			allowing(database).startTransaction();
@@ -1027,7 +1078,8 @@ public abstract class DatabaseComponentTest extends TestCase {
 			will(returnValue(batchId));
 			oneOf(database).addBatchToAck(txn, contactId, batchId);
 		}});
-		DatabaseComponent db = createDatabaseComponent(database, cleaner);
+		DatabaseComponent db = createDatabaseComponent(database, cleaner,
+				shutdown);
 
 		db.receiveBatch(contactId, batch);
 
@@ -1041,6 +1093,7 @@ public abstract class DatabaseComponentTest extends TestCase {
 		@SuppressWarnings("unchecked")
 		final Database<Object> database = context.mock(Database.class);
 		final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class);
+		final ShutdownManager shutdown = context.mock(ShutdownManager.class);
 		final Batch batch = context.mock(Batch.class);
 		context.checking(new Expectations() {{
 			allowing(database).startTransaction();
@@ -1063,7 +1116,8 @@ public abstract class DatabaseComponentTest extends TestCase {
 			will(returnValue(batchId));
 			oneOf(database).addBatchToAck(txn, contactId, batchId);
 		}});
-		DatabaseComponent db = createDatabaseComponent(database, cleaner);
+		DatabaseComponent db = createDatabaseComponent(database, cleaner,
+				shutdown);
 
 		db.receiveBatch(contactId, batch);
 
@@ -1076,6 +1130,7 @@ public abstract class DatabaseComponentTest extends TestCase {
 		@SuppressWarnings("unchecked")
 		final Database<Object> database = context.mock(Database.class);
 		final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class);
+		final ShutdownManager shutdown = context.mock(ShutdownManager.class);
 		final Batch batch = context.mock(Batch.class);
 		context.checking(new Expectations() {{
 			allowing(database).startTransaction();
@@ -1107,7 +1162,8 @@ public abstract class DatabaseComponentTest extends TestCase {
 			will(returnValue(batchId));
 			oneOf(database).addBatchToAck(txn, contactId, batchId);
 		}});
-		DatabaseComponent db = createDatabaseComponent(database, cleaner);
+		DatabaseComponent db = createDatabaseComponent(database, cleaner,
+				shutdown);
 
 		db.receiveBatch(contactId, batch);
 
@@ -1120,6 +1176,7 @@ public abstract class DatabaseComponentTest extends TestCase {
 		@SuppressWarnings("unchecked")
 		final Database<Object> database = context.mock(Database.class);
 		final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class);
+		final ShutdownManager shutdown = context.mock(ShutdownManager.class);
 		final Batch batch = context.mock(Batch.class);
 		context.checking(new Expectations() {{
 			allowing(database).startTransaction();
@@ -1153,7 +1210,8 @@ public abstract class DatabaseComponentTest extends TestCase {
 			will(returnValue(batchId));
 			oneOf(database).addBatchToAck(txn, contactId, batchId);
 		}});
-		DatabaseComponent db = createDatabaseComponent(database, cleaner);
+		DatabaseComponent db = createDatabaseComponent(database, cleaner,
+				shutdown);
 
 		db.receiveBatch(contactId, batch);
 
@@ -1175,6 +1233,7 @@ public abstract class DatabaseComponentTest extends TestCase {
 		@SuppressWarnings("unchecked")
 		final Database<Object> database = context.mock(Database.class);
 		final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class);
+		final ShutdownManager shutdown = context.mock(ShutdownManager.class);
 		final Offer offer = context.mock(Offer.class);
 		final RequestWriter requestWriter = context.mock(RequestWriter.class);
 		context.checking(new Expectations() {{
@@ -1194,7 +1253,8 @@ public abstract class DatabaseComponentTest extends TestCase {
 			will(returnValue(false)); // Not visible - request message # 2
 			oneOf(requestWriter).writeRequest(expectedRequest, 3);
 		}});
-		DatabaseComponent db = createDatabaseComponent(database, cleaner);
+		DatabaseComponent db = createDatabaseComponent(database, cleaner,
+				shutdown);
 
 		db.receiveOffer(contactId, offer, requestWriter);
 
@@ -1208,6 +1268,7 @@ public abstract class DatabaseComponentTest extends TestCase {
 		@SuppressWarnings("unchecked")
 		final Database<Object> database = context.mock(Database.class);
 		final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class);
+		final ShutdownManager shutdown = context.mock(ShutdownManager.class);
 		final SubscriptionUpdate subscriptionUpdate =
 			context.mock(SubscriptionUpdate.class);
 		context.checking(new Expectations() {{
@@ -1224,7 +1285,8 @@ public abstract class DatabaseComponentTest extends TestCase {
 			oneOf(database).setSubscriptions(txn, contactId,
 					Collections.singletonMap(group, 0L), timestamp);
 		}});
-		DatabaseComponent db = createDatabaseComponent(database, cleaner);
+		DatabaseComponent db = createDatabaseComponent(database, cleaner,
+				shutdown);
 
 		db.receiveSubscriptionUpdate(contactId, subscriptionUpdate);
 
@@ -1238,6 +1300,7 @@ public abstract class DatabaseComponentTest extends TestCase {
 		@SuppressWarnings("unchecked")
 		final Database<Object> database = context.mock(Database.class);
 		final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class);
+		final ShutdownManager shutdown = context.mock(ShutdownManager.class);
 		final TransportUpdate transportUpdate =
 			context.mock(TransportUpdate.class);
 		context.checking(new Expectations() {{
@@ -1254,7 +1317,8 @@ public abstract class DatabaseComponentTest extends TestCase {
 			oneOf(database).setTransports(txn, contactId, transports,
 					timestamp);
 		}});
-		DatabaseComponent db = createDatabaseComponent(database, cleaner);
+		DatabaseComponent db = createDatabaseComponent(database, cleaner,
+				shutdown);
 
 		db.receiveTransportUpdate(contactId, transportUpdate);
 
@@ -1267,6 +1331,7 @@ public abstract class DatabaseComponentTest extends TestCase {
 		@SuppressWarnings("unchecked")
 		final Database<Object> database = context.mock(Database.class);
 		final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class);
+		final ShutdownManager shutdown = context.mock(ShutdownManager.class);
 		final DatabaseListener listener = context.mock(DatabaseListener.class);
 		context.checking(new Expectations() {{
 			// addLocalGroupMessage(message)
@@ -1288,7 +1353,8 @@ public abstract class DatabaseComponentTest extends TestCase {
 			// The message was added, so the listener should be called
 			oneOf(listener).eventOccurred(with(any(MessagesAddedEvent.class)));
 		}});
-		DatabaseComponent db = createDatabaseComponent(database, cleaner);
+		DatabaseComponent db = createDatabaseComponent(database, cleaner,
+				shutdown);
 
 		db.addListener(listener);
 		db.addLocalGroupMessage(message);
@@ -1302,6 +1368,7 @@ public abstract class DatabaseComponentTest extends TestCase {
 		@SuppressWarnings("unchecked")
 		final Database<Object> database = context.mock(Database.class);
 		final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class);
+		final ShutdownManager shutdown = context.mock(ShutdownManager.class);
 		final DatabaseListener listener = context.mock(DatabaseListener.class);
 		context.checking(new Expectations() {{
 			allowing(database).startTransaction();
@@ -1316,7 +1383,8 @@ public abstract class DatabaseComponentTest extends TestCase {
 			// The message was added, so the listener should be called
 			oneOf(listener).eventOccurred(with(any(MessagesAddedEvent.class)));
 		}});
-		DatabaseComponent db = createDatabaseComponent(database, cleaner);
+		DatabaseComponent db = createDatabaseComponent(database, cleaner,
+				shutdown);
 
 		db.addListener(listener);
 		db.addLocalPrivateMessage(privateMessage, contactId);
@@ -1331,6 +1399,7 @@ public abstract class DatabaseComponentTest extends TestCase {
 		@SuppressWarnings("unchecked")
 		final Database<Object> database = context.mock(Database.class);
 		final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class);
+		final ShutdownManager shutdown = context.mock(ShutdownManager.class);
 		final DatabaseListener listener = context.mock(DatabaseListener.class);
 		context.checking(new Expectations() {{
 			// addLocalGroupMessage(message)
@@ -1343,7 +1412,8 @@ public abstract class DatabaseComponentTest extends TestCase {
 			oneOf(database).commitTransaction(txn);
 			// The message was not added, so the listener should not be called
 		}});
-		DatabaseComponent db = createDatabaseComponent(database, cleaner);
+		DatabaseComponent db = createDatabaseComponent(database, cleaner,
+				shutdown);
 
 		db.addListener(listener);
 		db.addLocalGroupMessage(message);
@@ -1358,6 +1428,7 @@ public abstract class DatabaseComponentTest extends TestCase {
 		@SuppressWarnings("unchecked")
 		final Database<Object> database = context.mock(Database.class);
 		final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class);
+		final ShutdownManager shutdown = context.mock(ShutdownManager.class);
 		final DatabaseListener listener = context.mock(DatabaseListener.class);
 		context.checking(new Expectations() {{
 			allowing(database).startTransaction();
@@ -1370,7 +1441,8 @@ public abstract class DatabaseComponentTest extends TestCase {
 			will(returnValue(false));
 			// The message was not added, so the listener should not be called
 		}});
-		DatabaseComponent db = createDatabaseComponent(database, cleaner);
+		DatabaseComponent db = createDatabaseComponent(database, cleaner,
+				shutdown);
 
 		db.addListener(listener);
 		db.addLocalPrivateMessage(privateMessage, contactId);
@@ -1387,6 +1459,7 @@ public abstract class DatabaseComponentTest extends TestCase {
 		@SuppressWarnings("unchecked")
 		final Database<Object> database = context.mock(Database.class);
 		final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class);
+		final ShutdownManager shutdown = context.mock(ShutdownManager.class);
 		final DatabaseListener listener = context.mock(DatabaseListener.class);
 		context.checking(new Expectations() {{
 			oneOf(database).startTransaction();
@@ -1400,7 +1473,8 @@ public abstract class DatabaseComponentTest extends TestCase {
 			oneOf(listener).eventOccurred(with(any(
 					TransportAddedEvent.class)));
 		}});
-		DatabaseComponent db = createDatabaseComponent(database, cleaner);
+		DatabaseComponent db = createDatabaseComponent(database, cleaner,
+				shutdown);
 
 		db.addListener(listener);
 		db.setLocalProperties(transportId, properties);
@@ -1417,6 +1491,7 @@ public abstract class DatabaseComponentTest extends TestCase {
 		@SuppressWarnings("unchecked")
 		final Database<Object> database = context.mock(Database.class);
 		final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class);
+		final ShutdownManager shutdown = context.mock(ShutdownManager.class);
 		final DatabaseListener listener = context.mock(DatabaseListener.class);
 		context.checking(new Expectations() {{
 			oneOf(database).startTransaction();
@@ -1425,7 +1500,8 @@ public abstract class DatabaseComponentTest extends TestCase {
 			will(returnValue(properties));
 			oneOf(database).commitTransaction(txn);
 		}});
-		DatabaseComponent db = createDatabaseComponent(database, cleaner);
+		DatabaseComponent db = createDatabaseComponent(database, cleaner,
+				shutdown);
 
 		db.addListener(listener);
 		db.setLocalProperties(transportId, properties);
@@ -1439,6 +1515,7 @@ public abstract class DatabaseComponentTest extends TestCase {
 		@SuppressWarnings("unchecked")
 		final Database<Object> database = context.mock(Database.class);
 		final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class);
+		final ShutdownManager shutdown = context.mock(ShutdownManager.class);
 		context.checking(new Expectations() {{
 			allowing(database).startTransaction();
 			will(returnValue(txn));
@@ -1448,7 +1525,8 @@ public abstract class DatabaseComponentTest extends TestCase {
 			// setSeen(contactId, Collections.singletonList(messageId))
 			oneOf(database).setStatusSeenIfVisible(txn, contactId, messageId);
 		}});
-		DatabaseComponent db = createDatabaseComponent(database, cleaner);
+		DatabaseComponent db = createDatabaseComponent(database, cleaner,
+				shutdown);
 
 		db.setSeen(contactId, Collections.singletonList(messageId));
 
diff --git a/test/net/sf/briar/db/H2DatabaseTest.java b/test/net/sf/briar/db/H2DatabaseTest.java
index dcb4062ea55d3c953b676fcad1394eab5585c5fd..728346e155cadf68f3e4fe54aef9c8f449cc1cf7 100644
--- a/test/net/sf/briar/db/H2DatabaseTest.java
+++ b/test/net/sf/briar/db/H2DatabaseTest.java
@@ -44,6 +44,7 @@ import net.sf.briar.api.transport.ConnectionContextFactory;
 import net.sf.briar.api.transport.ConnectionWindow;
 import net.sf.briar.api.transport.ConnectionWindowFactory;
 import net.sf.briar.crypto.CryptoModule;
+import net.sf.briar.lifecycle.LifecycleModule;
 import net.sf.briar.protocol.ProtocolModule;
 import net.sf.briar.protocol.writers.ProtocolWritersModule;
 import net.sf.briar.serial.SerialModule;
@@ -105,10 +106,11 @@ public class H2DatabaseTest extends TestCase {
 			}
 		};
 		Injector i = Guice.createInjector(testModule, new CryptoModule(),
-				new DatabaseModule(), new ProtocolModule(),
-				new ProtocolWritersModule(), new SerialModule(),
-				new TransportBatchModule(), new TransportModule(),
-				new TransportStreamModule(), new TestDatabaseModule(testDir));
+				new DatabaseModule(), new LifecycleModule(),
+				new ProtocolModule(), new ProtocolWritersModule(),
+				new SerialModule(), new TransportBatchModule(),
+				new TransportModule(), new TransportStreamModule(),
+				new TestDatabaseModule(testDir));
 		connectionContextFactory =
 			i.getInstance(ConnectionContextFactory.class);
 		connectionWindowFactory = i.getInstance(ConnectionWindowFactory.class);
diff --git a/test/net/sf/briar/lifecycle/ShutdownManagerImplTest.java b/test/net/sf/briar/lifecycle/ShutdownManagerImplTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..fc2482c9fa300e2118bca22a6fca1660e3391b40
--- /dev/null
+++ b/test/net/sf/briar/lifecycle/ShutdownManagerImplTest.java
@@ -0,0 +1,29 @@
+package net.sf.briar.lifecycle;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import junit.framework.TestCase;
+import net.sf.briar.api.lifecycle.ShutdownManager;
+
+import org.junit.Test;
+
+public class ShutdownManagerImplTest extends TestCase {
+
+	@Test
+	public void testAddAndRemove() {
+		ShutdownManager s = new ShutdownManagerImpl();
+		Set<Integer> handles = new HashSet<Integer>();
+		for(int i = 0; i < 100; i++) {
+			int handle = s.addShutdownHook(new Runnable() {
+				public void run() {}
+			});
+			// The handles should all be distinct
+			assertTrue(handles.add(handle));
+		}
+		// The hooks should be removable
+		for(int handle : handles) assertTrue(s.removeShutdownHook(handle));
+		// The hooks should no longer be removable
+		for(int handle : handles) assertFalse(s.removeShutdownHook(handle));
+	}
+}
diff --git a/test/net/sf/briar/transport/ConnectionRecogniserImplTest.java b/test/net/sf/briar/transport/ConnectionRecogniserImplTest.java
index 560fda09bd30529e2d45adf71cb8d1ea66931dbb..f0c5b8b765998a84c644b7ee1a4c82e13d67f8be 100644
--- a/test/net/sf/briar/transport/ConnectionRecogniserImplTest.java
+++ b/test/net/sf/briar/transport/ConnectionRecogniserImplTest.java
@@ -17,6 +17,7 @@ import net.sf.briar.api.crypto.CryptoComponent;
 import net.sf.briar.api.crypto.ErasableKey;
 import net.sf.briar.api.db.DatabaseComponent;
 import net.sf.briar.api.db.DbException;
+import net.sf.briar.api.lifecycle.ShutdownManager;
 import net.sf.briar.api.protocol.Transport;
 import net.sf.briar.api.protocol.TransportId;
 import net.sf.briar.api.protocol.TransportIndex;
@@ -65,9 +66,11 @@ public class ConnectionRecogniserImplTest extends TestCase {
 	public void testUnexpectedIv() throws Exception {
 		Mockery context = new Mockery();
 		final DatabaseComponent db = context.mock(DatabaseComponent.class);
+		final ShutdownManager shutdown = context.mock(ShutdownManager.class);
 		context.checking(new Expectations() {{
 			oneOf(db).addListener(with(any(ConnectionRecogniserImpl.class)));
 			// Initialise
+			oneOf(shutdown).addShutdownHook(with(any(Runnable.class)));
 			oneOf(db).getLocalTransports();
 			will(returnValue(transports));
 			oneOf(db).getContacts();
@@ -77,8 +80,9 @@ public class ConnectionRecogniserImplTest extends TestCase {
 			oneOf(db).getConnectionWindow(contactId, remoteIndex);
 			will(returnValue(connectionWindow));
 		}});
-		Executor e = new ImmediateExecutor();
-		ConnectionRecogniser c = new ConnectionRecogniserImpl(crypto, db, e);
+		Executor executor = new ImmediateExecutor();
+		ConnectionRecogniser c = new ConnectionRecogniserImpl(crypto, db,
+				executor, shutdown);
 		c.acceptConnection(transportId, new byte[IV_LENGTH], new Callback() {
 
 			public void connectionAccepted(ConnectionContext ctx) {
@@ -112,9 +116,11 @@ public class ConnectionRecogniserImplTest extends TestCase {
 
 		Mockery context = new Mockery();
 		final DatabaseComponent db = context.mock(DatabaseComponent.class);
+		final ShutdownManager shutdown = context.mock(ShutdownManager.class);
 		context.checking(new Expectations() {{
 			oneOf(db).addListener(with(any(ConnectionRecogniserImpl.class)));
 			// Initialise
+			oneOf(shutdown).addShutdownHook(with(any(Runnable.class)));
 			oneOf(db).getLocalTransports();
 			will(returnValue(transports));
 			oneOf(db).getContacts();
@@ -127,8 +133,9 @@ public class ConnectionRecogniserImplTest extends TestCase {
 			oneOf(db).setConnectionWindow(contactId, remoteIndex,
 					connectionWindow);
 		}});
-		Executor e = new ImmediateExecutor();
-		ConnectionRecogniser c = new ConnectionRecogniserImpl(crypto, db, e);
+		Executor executor = new ImmediateExecutor();
+		ConnectionRecogniser c = new ConnectionRecogniserImpl(crypto, db,
+				executor, shutdown);
 		// The IV should not be expected by the wrong transport
 		TransportId wrong = new TransportId(TestUtils.getRandomId());
 		c.acceptConnection(wrong, encryptedIv, new Callback() {
diff --git a/test/net/sf/briar/transport/ConnectionWriterTest.java b/test/net/sf/briar/transport/ConnectionWriterTest.java
index 416943b3d001c3b6c434e07c8f38782569c79f3e..9e47c43b9418fa0ffc2ed28adc898f29241673d5 100644
--- a/test/net/sf/briar/transport/ConnectionWriterTest.java
+++ b/test/net/sf/briar/transport/ConnectionWriterTest.java
@@ -18,6 +18,7 @@ import net.sf.briar.api.transport.ConnectionWriter;
 import net.sf.briar.api.transport.ConnectionWriterFactory;
 import net.sf.briar.crypto.CryptoModule;
 import net.sf.briar.db.DatabaseModule;
+import net.sf.briar.lifecycle.LifecycleModule;
 import net.sf.briar.protocol.ProtocolModule;
 import net.sf.briar.protocol.writers.ProtocolWritersModule;
 import net.sf.briar.serial.SerialModule;
@@ -50,10 +51,11 @@ public class ConnectionWriterTest extends TestCase {
 			}
 		};
 		Injector i = Guice.createInjector(testModule, new CryptoModule(),
-				new DatabaseModule(), new ProtocolModule(),
-				new ProtocolWritersModule(), new SerialModule(),
-				new TestDatabaseModule(), new TransportBatchModule(),
-				new TransportModule(), new TransportStreamModule());
+				new DatabaseModule(), new LifecycleModule(),
+				new ProtocolModule(), new ProtocolWritersModule(),
+				new SerialModule(), new TestDatabaseModule(),
+				new TransportBatchModule(), new TransportModule(),
+				new TransportStreamModule());
 		connectionContextFactory =
 			i.getInstance(ConnectionContextFactory.class);
 		connectionWriterFactory = i.getInstance(ConnectionWriterFactory.class);
diff --git a/test/net/sf/briar/transport/batch/BatchConnectionReadWriteTest.java b/test/net/sf/briar/transport/batch/BatchConnectionReadWriteTest.java
index 6fc6ccf193bb710592c747755411cc26d3349f31..45ce4c504fe051c7b1334e6d6595d62c7e064862 100644
--- a/test/net/sf/briar/transport/batch/BatchConnectionReadWriteTest.java
+++ b/test/net/sf/briar/transport/batch/BatchConnectionReadWriteTest.java
@@ -40,6 +40,7 @@ import net.sf.briar.api.transport.ConnectionRecogniser.Callback;
 import net.sf.briar.api.transport.ConnectionWriterFactory;
 import net.sf.briar.crypto.CryptoModule;
 import net.sf.briar.db.DatabaseModule;
+import net.sf.briar.lifecycle.LifecycleModule;
 import net.sf.briar.protocol.ProtocolModule;
 import net.sf.briar.protocol.writers.ProtocolWritersModule;
 import net.sf.briar.serial.SerialModule;
@@ -81,32 +82,24 @@ public class BatchConnectionReadWriteTest extends TestCase {
 	@Before
 	public void setUp() {
 		testDir.mkdirs();
-		// Create Alice's injector
-		Module aliceTestModule = new AbstractModule() {
-			@Override
-			public void configure() {
-				bind(Executor.class).toInstance(
-						new ScheduledThreadPoolExecutor(5));
-			}
-		};
-		alice = Guice.createInjector(aliceTestModule, new CryptoModule(),
-				new DatabaseModule(), new ProtocolModule(),
-				new ProtocolWritersModule(), new SerialModule(),
-				new TestDatabaseModule(aliceDir), new TransportBatchModule(),
-				new TransportModule(), new TransportStreamModule());
-		// Create Bob's injector
-		Module bobTestModule = new AbstractModule() {
+		alice = createInjector(aliceDir);
+		bob = createInjector(bobDir);
+	}
+
+	private Injector createInjector(File dir) {
+		Module testModule = new AbstractModule() {
 			@Override
 			public void configure() {
 				bind(Executor.class).toInstance(
 						new ScheduledThreadPoolExecutor(5));
 			}
 		};
-		bob = Guice.createInjector(bobTestModule, new CryptoModule(),
-				new DatabaseModule(), new ProtocolModule(),
-				new ProtocolWritersModule(), new SerialModule(),
-				new TestDatabaseModule(bobDir), new TransportBatchModule(),
-				new TransportModule(), new TransportStreamModule());
+		return Guice.createInjector(testModule, new CryptoModule(),
+				new DatabaseModule(), new LifecycleModule(),
+				new ProtocolModule(), new ProtocolWritersModule(),
+				new SerialModule(), new TestDatabaseModule(dir),
+				new TransportBatchModule(), new TransportModule(),
+				new TransportStreamModule());
 	}
 
 	@Test