Encapsulate the database thread pool and task queue.

This commit is contained in:
akwizgran
2011-12-07 14:34:16 +00:00
parent a9a81f0870
commit 5099979b9d
5 changed files with 278 additions and 167 deletions

View File

@@ -0,0 +1,15 @@
package net.sf.briar.api.db;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import com.google.inject.BindingAnnotation;
/** Annotation for injecting the executor for database tasks. */
@BindingAnnotation
@Target({ PARAMETER })
@Retention(RUNTIME)
public @interface DatabaseExecutor {}

View File

@@ -0,0 +1,44 @@
package net.sf.briar.db;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
class DatabaseExecutorImpl implements Executor {
// FIXME: Determine suitable values for these constants empirically
/**
* The maximum number of tasks that can be queued for execution
* before attempting to execute another task will block.
*/
private static final int MAX_QUEUED_TASKS = 10;
/** The number of idle threads to keep in the pool. */
private static final int MIN_THREADS = 1;
/** The maximum number of concurrent tasks. */
private static final int MAX_THREADS = 10;
private final BlockingQueue<Runnable> queue;
DatabaseExecutorImpl() {
this(MAX_QUEUED_TASKS, MIN_THREADS, MAX_THREADS);
}
DatabaseExecutorImpl(int maxQueuedTasks, int minThreads, int maxThreads) {
queue = new ArrayBlockingQueue<Runnable>(maxQueuedTasks);
new ThreadPoolExecutor(minThreads, maxThreads, 60, TimeUnit.SECONDS,
queue);
}
public void execute(Runnable r) {
try {
queue.put(r);
} catch(InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}

View File

@@ -2,10 +2,12 @@ package net.sf.briar.db;
import java.io.File;
import java.sql.Connection;
import java.util.concurrent.Executor;
import net.sf.briar.api.crypto.Password;
import net.sf.briar.api.db.DatabaseComponent;
import net.sf.briar.api.db.DatabaseDirectory;
import net.sf.briar.api.db.DatabaseExecutor;
import net.sf.briar.api.db.DatabaseMaxSize;
import net.sf.briar.api.db.DatabasePassword;
import net.sf.briar.api.lifecycle.ShutdownManager;
@@ -23,6 +25,8 @@ public class DatabaseModule extends AbstractModule {
@Override
protected void configure() {
bind(DatabaseCleaner.class).to(DatabaseCleanerImpl.class);
bind(Executor.class).annotatedWith(DatabaseExecutor.class).to(
DatabaseExecutorImpl.class).in(Singleton.class);
}
@Provides

View File

@@ -3,13 +3,13 @@ package net.sf.briar.transport.batch;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.util.concurrent.Executor;
import java.util.concurrent.Semaphore;
import java.util.logging.Level;
import java.util.logging.Logger;
import net.sf.briar.api.ContactId;
import net.sf.briar.api.FormatException;
import net.sf.briar.api.db.DatabaseComponent;
import net.sf.briar.api.db.DatabaseExecutor;
import net.sf.briar.api.db.DbException;
import net.sf.briar.api.protocol.Ack;
import net.sf.briar.api.protocol.ProtocolReader;
@@ -24,32 +24,28 @@ import net.sf.briar.api.transport.ConnectionReaderFactory;
class IncomingBatchConnection {
private static final int MAX_WAITING_DB_WRITES = 5;
private static final Logger LOG =
Logger.getLogger(IncomingBatchConnection.class.getName());
private final Executor executor;
private final Executor dbExecutor;
private final ConnectionReaderFactory connFactory;
private final DatabaseComponent db;
private final ProtocolReaderFactory protoFactory;
private final ConnectionContext ctx;
private final BatchTransportReader reader;
private final byte[] tag;
private final Semaphore semaphore;
IncomingBatchConnection(Executor executor,
DatabaseComponent db,
ConnectionReaderFactory connFactory, ProtocolReaderFactory protoFactory,
ConnectionContext ctx, BatchTransportReader reader, byte[] tag) {
this.executor = executor;
IncomingBatchConnection(@DatabaseExecutor Executor dbExecutor,
DatabaseComponent db, ConnectionReaderFactory connFactory,
ProtocolReaderFactory protoFactory, ConnectionContext ctx,
BatchTransportReader reader, byte[] tag) {
this.dbExecutor = dbExecutor;
this.connFactory = connFactory;
this.db = db;
this.protoFactory = protoFactory;
this.ctx = ctx;
this.reader = reader;
this.tag = tag;
semaphore = new Semaphore(MAX_WAITING_DB_WRITES);
}
void read() {
@@ -62,78 +58,21 @@ class IncomingBatchConnection {
// Read packets until EOF
while(!proto.eof()) {
if(proto.hasAck()) {
final Ack a = proto.readAck();
// Store the ack on another thread
semaphore.acquire();
executor.execute(new Runnable() {
public void run() {
try {
db.receiveAck(c, a);
} catch(DbException e) {
if(LOG.isLoggable(Level.WARNING))
LOG.warning(e.getMessage());
} finally {
semaphore.release();
}
}
});
Ack a = proto.readAck();
dbExecutor.execute(new ReceiveAck(c, a));
} else if(proto.hasBatch()) {
final UnverifiedBatch b = proto.readBatch();
// Verify and store the batch on another thread
semaphore.acquire();
executor.execute(new Runnable() {
public void run() {
try {
db.receiveBatch(c, b.verify());
} catch(DbException e) {
if(LOG.isLoggable(Level.WARNING))
LOG.warning(e.getMessage());
} catch(GeneralSecurityException e) {
if(LOG.isLoggable(Level.WARNING))
LOG.warning(e.getMessage());
} finally {
semaphore.release();
}
}
});
UnverifiedBatch b = proto.readBatch();
dbExecutor.execute(new ReceiveBatch(c, b));
} else if(proto.hasSubscriptionUpdate()) {
final SubscriptionUpdate s = proto.readSubscriptionUpdate();
// Store the update on another thread
semaphore.acquire();
executor.execute(new Runnable() {
public void run() {
try {
db.receiveSubscriptionUpdate(c, s);
} catch(DbException e) {
if(LOG.isLoggable(Level.WARNING))
LOG.warning(e.getMessage());
} finally {
semaphore.release();
}
}
});
SubscriptionUpdate s = proto.readSubscriptionUpdate();
dbExecutor.execute(new ReceiveSubscriptionUpdate(c, s));
} else if(proto.hasTransportUpdate()) {
final TransportUpdate t = proto.readTransportUpdate();
// Store the update on another thread
semaphore.acquire();
executor.execute(new Runnable() {
public void run() {
try {
db.receiveTransportUpdate(c, t);
} catch(DbException e) {
if(LOG.isLoggable(Level.WARNING))
LOG.warning(e.getMessage());
} finally {
semaphore.release();
}
}
});
TransportUpdate t = proto.readTransportUpdate();
dbExecutor.execute(new ReceiveTransportUpdate(c, t));
} else {
throw new FormatException();
}
}
} catch(InterruptedException e) {
Thread.currentThread().interrupt();
} catch(IOException e) {
if(LOG.isLoggable(Level.WARNING)) LOG.warning(e.getMessage());
reader.dispose(false);
@@ -141,4 +80,85 @@ class IncomingBatchConnection {
// Success
reader.dispose(true);
}
private class ReceiveAck implements Runnable {
private final ContactId contactId;
private final Ack ack;
private ReceiveAck(ContactId contactId, Ack ack) {
this.contactId = contactId;
this.ack = ack;
}
public void run() {
try {
db.receiveAck(contactId, ack);
} catch(DbException e) {
if(LOG.isLoggable(Level.WARNING)) LOG.warning(e.getMessage());
}
}
}
private class ReceiveBatch implements Runnable {
private final ContactId contactId;
private final UnverifiedBatch batch;
private ReceiveBatch(ContactId contactId, UnverifiedBatch batch) {
this.contactId = contactId;
this.batch = batch;
}
public void run() {
try {
// FIXME: Don't verify on the DB thread
db.receiveBatch(contactId, batch.verify());
} catch(DbException e) {
if(LOG.isLoggable(Level.WARNING)) LOG.warning(e.getMessage());
} catch(GeneralSecurityException e) {
if(LOG.isLoggable(Level.WARNING)) LOG.warning(e.getMessage());
}
}
}
private class ReceiveSubscriptionUpdate implements Runnable {
private final ContactId contactId;
private final SubscriptionUpdate update;
private ReceiveSubscriptionUpdate(ContactId contactId,
SubscriptionUpdate update) {
this.contactId = contactId;
this.update = update;
}
public void run() {
try {
db.receiveSubscriptionUpdate(contactId, update);
} catch(DbException e) {
if(LOG.isLoggable(Level.WARNING)) LOG.warning(e.getMessage());
}
}
}
private class ReceiveTransportUpdate implements Runnable {
private final ContactId contactId;
private final TransportUpdate update;
private ReceiveTransportUpdate(ContactId contactId,
TransportUpdate update) {
this.contactId = contactId;
this.update = update;
}
public void run() {
try {
db.receiveTransportUpdate(contactId, update);
} catch(DbException e) {
if(LOG.isLoggable(Level.WARNING)) LOG.warning(e.getMessage());
}
}
}
}

View File

@@ -11,13 +11,13 @@ import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.concurrent.Semaphore;
import java.util.logging.Level;
import java.util.logging.Logger;
import net.sf.briar.api.ContactId;
import net.sf.briar.api.FormatException;
import net.sf.briar.api.db.DatabaseComponent;
import net.sf.briar.api.db.DatabaseExecutor;
import net.sf.briar.api.db.DbException;
import net.sf.briar.api.db.event.BatchReceivedEvent;
import net.sf.briar.api.db.event.ContactRemovedEvent;
@@ -47,14 +47,12 @@ import net.sf.briar.api.transport.StreamTransportConnection;
abstract class StreamConnection implements DatabaseListener {
private static final int MAX_WAITING_DB_WRITES = 5;
private static enum State { SEND_OFFER, IDLE, AWAIT_REQUEST, SEND_BATCHES };
private static final Logger LOG =
Logger.getLogger(StreamConnection.class.getName());
protected final Executor executor;
protected final Executor dbExecutor;
protected final DatabaseComponent db;
protected final SerialComponent serial;
protected final ConnectionReaderFactory connReaderFactory;
@@ -64,20 +62,19 @@ abstract class StreamConnection implements DatabaseListener {
protected final ContactId contactId;
protected final StreamTransportConnection connection;
private final Semaphore semaphore;
private int writerFlags = 0; // Locking: this
private Collection<MessageId> offered = null; // Locking: this
private LinkedList<MessageId> requested = null; // Locking: this
private Offer incomingOffer = null; // Locking: this
StreamConnection(Executor executor, DatabaseComponent db,
SerialComponent serial, ConnectionReaderFactory connReaderFactory,
StreamConnection(@DatabaseExecutor Executor dbExecutor,
DatabaseComponent db, SerialComponent serial,
ConnectionReaderFactory connReaderFactory,
ConnectionWriterFactory connWriterFactory,
ProtocolReaderFactory protoReaderFactory,
ProtocolWriterFactory protoWriterFactory, ContactId contactId,
StreamTransportConnection connection) {
this.executor = executor;
this.dbExecutor = dbExecutor;
this.db = db;
this.serial = serial;
this.connReaderFactory = connReaderFactory;
@@ -86,7 +83,6 @@ abstract class StreamConnection implements DatabaseListener {
this.protoWriterFactory = protoWriterFactory;
this.contactId = contactId;
this.connection = connection;
semaphore = new Semaphore(MAX_WAITING_DB_WRITES);
}
protected abstract ConnectionReader createConnectionReader()
@@ -129,40 +125,11 @@ abstract class StreamConnection implements DatabaseListener {
ProtocolReader proto = protoReaderFactory.createProtocolReader(in);
while(!proto.eof()) {
if(proto.hasAck()) {
final Ack a = proto.readAck();
// Store the ack on another thread
semaphore.acquire();
executor.execute(new Runnable() {
public void run() {
try {
db.receiveAck(contactId, a);
} catch(DbException e) {
if(LOG.isLoggable(Level.WARNING))
LOG.warning(e.getMessage());
} finally {
semaphore.release();
}
}
});
Ack a = proto.readAck();
dbExecutor.execute(new ReceiveAck(contactId, a));
} else if(proto.hasBatch()) {
final UnverifiedBatch b = proto.readBatch();
// Verify and store the batch on another thread
semaphore.acquire();
executor.execute(new Runnable() {
public void run() {
try {
db.receiveBatch(contactId, b.verify());
} catch(DbException e) {
if(LOG.isLoggable(Level.WARNING))
LOG.warning(e.getMessage());
} catch(GeneralSecurityException e) {
if(LOG.isLoggable(Level.WARNING))
LOG.warning(e.getMessage());
} finally {
semaphore.release();
}
}
});
UnverifiedBatch b = proto.readBatch();
dbExecutor.execute(new ReceiveBatch(contactId, b));
} else if(proto.hasOffer()) {
Offer o = proto.readOffer();
// Store the incoming offer and notify the writer
@@ -190,22 +157,9 @@ abstract class StreamConnection implements DatabaseListener {
if(b.get(i++)) req.add(m);
else seen.add(m);
}
// Mark the unrequested messages as seen on another thread
final List<MessageId> l =
Collections.unmodifiableList(seen);
semaphore.acquire();
executor.execute(new Runnable() {
public void run() {
try {
db.setSeen(contactId, l);
} catch(DbException e) {
if(LOG.isLoggable(Level.WARNING))
LOG.warning(e.getMessage());
} finally {
semaphore.release();
}
}
});
seen = Collections.unmodifiableList(seen);
// Mark the unrequested messages as seen
dbExecutor.execute(new SetSeen(contactId, seen));
// Store the requested message IDs and notify the writer
synchronized(this) {
if(requested != null)
@@ -215,37 +169,13 @@ abstract class StreamConnection implements DatabaseListener {
notifyAll();
}
} else if(proto.hasSubscriptionUpdate()) {
final SubscriptionUpdate s = proto.readSubscriptionUpdate();
// Store the update on another thread
semaphore.acquire();
executor.execute(new Runnable() {
public void run() {
try {
db.receiveSubscriptionUpdate(contactId, s);
} catch(DbException e) {
if(LOG.isLoggable(Level.WARNING))
LOG.warning(e.getMessage());
} finally {
semaphore.release();
}
}
});
SubscriptionUpdate s = proto.readSubscriptionUpdate();
dbExecutor.execute(new ReceiveSubscriptionUpdate(
contactId, s));
} else if(proto.hasTransportUpdate()) {
final TransportUpdate t = proto.readTransportUpdate();
// Store the update on another thread
semaphore.acquire();
executor.execute(new Runnable() {
public void run() {
try {
db.receiveTransportUpdate(contactId, t);
} catch(DbException e) {
if(LOG.isLoggable(Level.WARNING))
LOG.warning(e.getMessage());
} finally {
semaphore.release();
}
}
});
TransportUpdate t = proto.readTransportUpdate();
dbExecutor.execute(new ReceiveTransportUpdate(
contactId, t));
} else {
throw new FormatException();
}
@@ -253,8 +183,6 @@ abstract class StreamConnection implements DatabaseListener {
} catch(DbException e) {
if(LOG.isLoggable(Level.WARNING)) LOG.warning(e.getMessage());
connection.dispose(false);
} catch(InterruptedException e) {
Thread.currentThread().interrupt();
} catch(IOException e) {
if(LOG.isLoggable(Level.WARNING)) LOG.warning(e.getMessage());
connection.dispose(false);
@@ -483,4 +411,104 @@ abstract class StreamConnection implements DatabaseListener {
SubscriptionUpdate s = db.generateSubscriptionUpdate(contactId);
if(s != null) proto.writeSubscriptionUpdate(s);
}
private class ReceiveAck implements Runnable {
private final ContactId contactId;
private final Ack ack;
private ReceiveAck(ContactId contactId, Ack ack) {
this.contactId = contactId;
this.ack = ack;
}
public void run() {
try {
db.receiveAck(contactId, ack);
} catch(DbException e) {
if(LOG.isLoggable(Level.WARNING)) LOG.warning(e.getMessage());
}
}
}
private class ReceiveBatch implements Runnable {
private final ContactId contactId;
private final UnverifiedBatch batch;
private ReceiveBatch(ContactId contactId, UnverifiedBatch batch) {
this.contactId = contactId;
this.batch = batch;
}
public void run() {
try {
// FIXME: Don't verify on the DB thread
db.receiveBatch(contactId, batch.verify());
} catch(DbException e) {
if(LOG.isLoggable(Level.WARNING)) LOG.warning(e.getMessage());
} catch(GeneralSecurityException e) {
if(LOG.isLoggable(Level.WARNING)) LOG.warning(e.getMessage());
}
}
}
private class SetSeen implements Runnable {
private final ContactId contactId;
private final Collection<MessageId> seen;
private SetSeen(ContactId contactId, Collection<MessageId> seen) {
this.contactId = contactId;
this.seen = seen;
}
public void run() {
try {
db.setSeen(contactId, seen);
} catch(DbException e) {
if(LOG.isLoggable(Level.WARNING)) LOG.warning(e.getMessage());
}
}
}
private class ReceiveSubscriptionUpdate implements Runnable {
private final ContactId contactId;
private final SubscriptionUpdate update;
private ReceiveSubscriptionUpdate(ContactId contactId,
SubscriptionUpdate update) {
this.contactId = contactId;
this.update = update;
}
public void run() {
try {
db.receiveSubscriptionUpdate(contactId, update);
} catch(DbException e) {
if(LOG.isLoggable(Level.WARNING)) LOG.warning(e.getMessage());
}
}
}
private class ReceiveTransportUpdate implements Runnable {
private final ContactId contactId;
private final TransportUpdate update;
private ReceiveTransportUpdate(ContactId contactId,
TransportUpdate update) {
this.contactId = contactId;
this.update = update;
}
public void run() {
try {
db.receiveTransportUpdate(contactId, update);
} catch(DbException e) {
if(LOG.isLoggable(Level.WARNING)) LOG.warning(e.getMessage());
}
}
}
}