Shutdown manager (untested on Windows).

This commit is contained in:
akwizgran
2011-11-18 17:13:55 +00:00
parent 859ece6328
commit 046becd388
22 changed files with 467 additions and 96 deletions

View File

@@ -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) {

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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());

View File

@@ -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());

View File

@@ -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();
}

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;